Detail Views
Shape the read-first admin screen editors see before they open a collection record or global for editing.
A Detail View gives editors a readable summary of a record or global before they open the full edit form. Use it when a document has important status, repeated data, computed totals, or nested settings that are easier to review as a dashboard than as one long form.
By the end of this page you will understand what Dyrected renders by default, how to build a tailored layout using display helpers, how to enable quick inline edits, and every option available across each helper function.
How Detail Views fit into the admin
Collection records and globals have two complementary modes in the admin:
- The Detail View is for reading, reviewing, and making quick inline adjustments to the current document.
- The Edit View is for deep editing and modifying the complete form.
When a collection or global does not define detail, Dyrected automatically generates a sensible layout from its fields. That default layout skips sensitive credentials (passwords, salts, reset tokens, API keys) and positions metadata (statuses, booleans, dates) into a tidy summary.
If a document does not need a read-first screen, set detail: false. Editors will navigate straight to the edit form instead.
import { defineCollection, defineTextField } from "@dyrected/core";
export const Pages = defineCollection({
slug: "pages",
labels: { singular: "Page", plural: "Pages" },
admin: {
useAsTitle: "title",
},
detail: false,
fields: [
defineTextField({ name: "title", label: "Title", required: true }),
defineTextField({ name: "slug", label: "Slug", required: true, unique: true }),
],
});That is useful for content that editors almost always need to modify immediately, such as page builder documents with large block fields.
Build a first custom Detail View
The recommended starting point is a few sections with fields inside them. Each item can use span (out of 12 columns) to control how much width it occupies.
import {
defineCollection,
defineNumberField,
defineSelectField,
defineTextareaField,
defineTextField,
displayField,
displaySection,
} from "@dyrected/core";
export const Products = defineCollection({
slug: "products",
labels: { singular: "Product", plural: "Products" },
admin: {
useAsTitle: "title",
defaultColumns: ["title", "sku", "price", "status"],
},
detail: [
displaySection(
"Product Overview",
[
displayField("title", { span: 8 }),
displayField("sku", { span: 4, display: "copyable" }),
displayField("description", { span: 12 }),
],
{ span: 8, icon: "Package" },
),
displaySection(
"Pricing & Status",
[
displayField("price", { span: 6, display: "currency", currency: "USD", editable: true }),
displayField("status", {
span: 6,
display: "badge",
badgeColors: { active: "emerald", draft: "zinc", archived: "rose" },
editable: true,
}),
],
{ span: 4, badge: "Live", badgeColor: "emerald" },
),
],
fields: [
defineTextField({ name: "title", label: "Title", required: true }),
defineTextField({ name: "sku", label: "SKU", required: true }),
defineTextareaField({ name: "description", label: "Description" }),
defineNumberField({ name: "price", label: "Price", required: true }),
defineSelectField({
name: "status",
label: "Status",
options: [
{ label: "Active", value: "active" },
{ label: "Draft", value: "draft" },
{ label: "Archived", value: "archived" },
],
defaultValue: "draft",
}),
],
});In this example, the overview section occupies eight columns and the pricing section occupies four columns on desktop screens. Inside each section, fields use the same 12-column grid.
Global Detail View (Singleton Document)
Detail Views work identically for globals. Use them to create a clean overview dashboard for site settings, company metadata, or theme configuration:
import {
defineArrayField,
defineEmailField,
defineGlobal,
defineNumberField,
defineTextField,
defineUrlField,
displayComputed,
displayField,
displaySection,
} from "@dyrected/core";
export const SiteSettings = defineGlobal({
slug: "site-settings",
label: "Site Settings",
detail: [
displaySection(
"Identity & Contact",
[
displayField("siteName", { span: 6, editable: true }),
displayField("supportEmail", { span: 6, display: "email", editable: true }),
displayField("rating", { span: 6, display: "star-rating" }),
displayField("primaryIcon", { span: 6, display: "icon" }),
],
{ span: 8, icon: "Settings" },
),
displaySection(
"Quick Stats",
[displayComputed("Total Social Links", "count(doc.socialLinks)", { span: 12 })],
{ span: 4 },
),
],
fields: [
defineTextField({ name: "siteName", label: "Site Name", required: true }),
defineEmailField({ name: "supportEmail", label: "Support Email", required: true }),
defineNumberField({ name: "rating", label: "Customer Rating", defaultValue: 5 }),
defineTextField({ name: "primaryIcon", label: "Primary Icon", defaultValue: "Globe" }),
defineArrayField({
name: "socialLinks",
label: "Social Links",
fields: [
defineTextField({ name: "platform", label: "Platform" }),
defineUrlField({ name: "url", label: "URL" }),
],
}),
],
});Inline editing with editable
When an editor only needs to tweak a minor note, toggle a status, or update a price, opening the full edit page can feel heavy. Set editable: true on any displayField() to enable an inline edit toggle directly on the Detail View.
displayField("internalNotes", {
label: "Internal Reviewer Notes",
span: 12,
editable: true,
})
displayField("stock", {
label: "Available Units",
span: 6,
editable: true,
})
displayField("accentColor", {
label: "Theme Accent",
span: 6,
display: "color",
editable: true,
})When editable: true is set:
- Hovering or focusing on the field reveals a subtle pencil edit button.
- Clicking the pencil opens the dedicated interactive input (e.g. text input, multiline textarea, select dropdown, checkbox, date picker, or color swatch).
- Clicking Save (✓) triggers an immediate document update via the Dyrected Client SDK and displays a confirmation toast without leaving the screen.
- Clicking Cancel (✕) reverts to the existing value without making network calls.
Supported inline editable field types include text, textareas, numbers, currencies, percentages, selects, radio buttons, booleans, dates, datetimes, colors, and tag lists.
Custom badge colors with badgeColors
When displaying statuses, priority levels, or tags using display: "badge", display: "badges", or display: "tags", you can map raw document values to distinct color tones with badgeColors.
displayField("orderStatus", {
display: "badge",
badgeColors: {
delivered: "emerald",
processing: "sky",
pending: "amber",
cancelled: "rose",
"*": "zinc", // Fallback color for any other value
},
})Supported color palettes
badgeColors accepts:
- Named color palettes:
emerald,green,success,amber,yellow,warning,rose,red,danger,destructive,blue,sky,cyan,info,indigo,violet,purple,fuchsia,pink,teal,orange,zinc,gray,slate,neutral,primary,secondary. - Custom CSS / Hex colors: e.g.
badgeColors: { VIP: "#8b5cf6", Enterprise: "#0ea5e9" }. - Tailwind class strings: e.g.
badgeColors: { custom: "dy-bg-indigo-500/15 dy-text-indigo-600" }. - Wildcard fallback (
*ordefault): applies a consistent fallback palette whenever the document contains an unmapped value.
Arrange content with layout helpers
Sections with displaySection
displaySection(title, items, options) groups fields and other detail items under a titled card. Sections can have an icon, a badge, a subtitle description, a grid span, and collapsible behavior.
displaySection(
"Publishing Status",
[
displayField("status", { span: 6, display: "badge" }),
displayField("publishedAt", { span: 6, display: "relative" }),
],
{
span: 12,
icon: "CalendarCheck",
badge: "Verified",
badgeColor: "emerald",
description: "Review publication state before scheduling social campaigns.",
collapsible: true,
collapsedByDefault: false,
},
);Tabs with displayTabs and displayTab
displayTabs() and displayTab() split large records into focused tabs. Individual tabs can display icons and notification badges.
displayTabs(
[
displayTab("Specifications", [
displayField("dimensions", { span: 6 }),
displayField("weight", { span: 6 }),
], { icon: "Cpu" }),
displayTab("Reviews", [
displayRepeat("reviews", [
displayField("reviewer", { span: 6 }),
displayField("rating", { span: 6, display: "star-rating" }),
displayField("comment", { span: 12 }),
], { layout: "cards" }),
], {
icon: "Star",
badge: "count(doc.reviews)",
badgeColor: "amber",
}),
],
{ span: 12, defaultTab: "Specifications" },
);Grids with displayGrid
displayGrid(columns, items, options) creates a balanced multi-column grid inside sections or tabs.
displayGrid(
3,
[
displayField("views", { display: "number" }),
displayField("conversionRate", { display: "percent" }),
displayField("featured", { display: "boolean" }),
],
{ span: 12 },
);Display fields and visual variants
displayField(fieldName, options) chooses how to present a specific property. The fieldName can point to a top-level field or a nested dotted path (user.profile.avatar).
displayField("author.name", { label: "Author", span: 6 });
displayField("price", { display: "currency", currency: "USD", span: 6 });
displayField("rating", { display: "star-rating", span: 6 });
displayField("brandColor", { display: "color", span: 6 });
displayField("palette", { display: "color-swatches", span: 12 });
displayField("apiSecret", { display: "copyable", span: 6 });
displayField("coverImage", { display: "image", aspectRatio: "16/9", objectFit: "cover", align: "center", span: 12 });Display variants overview
| Category | Variants | What it renders |
|---|---|---|
| Status & Badges | badge, badges, tags, boolean | Colored status pills and boolean check indicators. |
| Numbers & Metrics | currency, percent, progress, star, star-rating | Formatted money, percentage bars, and star ratings. |
| Dates & Times | date, datetime, time, relative | Localized dates, timestamps, and relative time ("2 hours ago"). |
| Links & Contact | email, phone, url, link, copyable | Clickable mailto:, tel:, external links, and copyable text with a one-click clipboard button. |
| Media & Colors | image, avatar, color, color-swatches, icon | Image thumbnails with full-preview links, circular avatars, color swatch preview boxes, and Lucide icons. |
| Structured Data | key-value, table, json, code, code-badge | 2-column key-value tables, syntax-highlighted code blocks, and monospace badges. |
Empty value handling
By default, missing or empty values display a clean dash (—). You can customize this per field:
emptyText: "Not assigned": replaces the default dash with custom descriptive text.hideIfEmpty: true: completely omits the field row when the value is null, undefined, or empty.
Add computed totals with displayComputed
Use displayComputed(label, expressionOrHandler, options) to display metrics derived from document values or relationships.
Pass a JEXL expression for calculated stats:
displayComputed("Total Categories", "count(doc.categories)", { span: 6 });
displayComputed("Estimated Profit", "doc.price - doc.cost", {
span: 6,
format: "currency",
currency: "USD",
});Show repeated line items with displayRepeat
Use displayRepeat(fieldName, items, options) to render array fields, order line items, or sub-documents.
displayRepeat(
"orderItems",
[
displayField("productName", { span: 6 }),
displayField("quantity", { span: 3 }),
displayField("price", { span: 3, display: "currency" }),
],
{
layout: "cards",
columns: 3,
useAsTitle: "productName",
emptyText: "No line items in this order",
},
);Repeat layouts include:
table: compact table with column headers (default).cards: individual grid cards with titles and icons.list: vertical feed of line items.
Add notices, dividers, and custom components
displayText and displayDivider
import { displayDivider, displayText } from "@dyrected/core";
export const reviewNotice = [
displayText("Verify shipping addresses before changing order status to Shipped.", {
variant: "warning",
span: 12,
}),
displayDivider({ spacing: "md" }),
];displayText() supports body (default), heading, subheading, muted, caption, callout, info, and warning. displayDivider() accepts spacing: "none" | "sm" | "md" | "lg".
displayCustom
Mount custom React components registered into the admin:
displayCustom("CustomerAnalyticsChart", {
span: 12,
props: { period: "30d" },
})Conditionally show items with visible
Every detail item supports a visible condition, which can be a boolean or a JEXL expression evaluated against { doc, user }:
displaySection(
"Financial Summary",
[
displayField("totalRevenue", { span: 6, display: "currency" }),
displayField("auditNotes", {
span: 6,
visible: "user.roles != null and includes(user.roles, 'admin')",
}),
],
{
span: 12,
visible: "doc.status == 'published'",
},
);UX best practices for fast-scanning Detail Views
A Detail View should feel like an operational dashboard tailored to the person managing the record. Follow these battle-tested UX patterns when designing layouts for your collections:
1. The "First 2 Inches" rule (glanceable top summary)
Avoid starting your layout with a heavy card or repeating the document title. Directly beneath the title bar, use a lightweight, borderless displayGrid(4, [...]) to present high-signal indicators in the first screen:
- Primary Status Pill: (
display: "badge") - Secondary State: Door check-in, fulfillment, stock level, or customer tier
- Primary Channels: Clickable phone (
display: "phone"), email (display: "email"), or reference ID (display: "copyable")
// Tightly grouped 4-column summary bar across desktop; reflows smoothly on mobile
displayGrid(4, [
displayField("status", {
display: "badge",
badgeColors: { active: "emerald", draft: "zinc", archived: "rose" },
label: "Status",
}),
displayField("priority", { display: "badge", label: "Priority" }),
displayField("phone", { display: "phone", label: "Phone" }),
displayField("email", { display: "email", label: "Email" }),
]),2. Avoid card fatigue with borderless grids and dividers
Wrapping every small group of two or three fields in a displaySection card adds unnecessary container padding, borders, and header weight.
- Use
displayGrid+displayDivider({ spacing: "md" })for the continuous primary narrative (Contact, Key Details, Action Hub). - Reserve
displaySectionfor complex, multi-field subsystems that benefit from distinct boundaries, status badges, or collapsible drawers (such as Financial Breakdowns, Shipping Logistics, or Reviewer Notes).
3. Place hero action tools where the eye lands
Treat the Detail View as an operational command center. If operators frequently perform tasks for a record (such as generating a QR pass, triggering a WhatsApp notification, or copying a client access URL), embed those tools directly into the flow using displayCustomComponent() rather than hiding them in menus.
// Hero actions placed directly below the top identity strip
displayCustomComponent("AccessCardPreview"),
displayCustomComponent("SendWhatsAppButton"),4. Enable micro-interactions with editable: true
High-velocity fields that change frequently—such as toggling an order status from "pending" to "received", modifying inventory counts, or appending quick delivery notes—should not require opening the full edit page. Set editable: true on these fields to allow instantaneous inline updates without navigation.
displayField("paymentStatus", {
display: "badge",
badgeColors: { received: "emerald", pending: "amber", waived: "purple" },
editable: true,
}),
displayField("deliveryNotes", {
editable: true,
hideIfEmpty: true,
}),5. Progressive conditional disclosure with visible
Clean dashboards omit irrelevant data. Use JEXL visibility expressions to conditionally hide entire sections or fields when they do not apply to the current record.
// Hide the entire fulfillment card if the guest didn't place an order
displaySection("Order & Fulfillment", [
// ... fields
], {
span: 12,
icon: "Package",
visible: "doc.wantsAsoebi || doc.wantsAsoOke",
}),6. Mobile-first stacking priority
Always order items in the detail array in the exact sequential priority an on-the-go operator needs when viewing the record on a phone:
- Identity & Immediate Status: Who is this and what is their current state?
- Primary Action Tools: Instant pass preview, dispatch trigger, scanner.
- Operational Details: Order quantities, payment breakdowns, fulfillment cards.
- Context & Personal Notes: Well-wishes, message notes, events schedule.
- Timeline & Audit Metadata: Submitted date, author ID, timestamps.
Complete Helper & Options Reference
1. displayField(fieldName, options?)
Declares an individual field to render in the Detail View. It supports raw fields, nested dotted paths (e.g. 'author.name'), and virtual/computed fields.
import { displayField } from "@dyrected/core";
displayField("status", {
label: "Publication Status",
display: "badge",
badgeColors: {
published: "emerald",
draft: "zinc",
archived: "rose",
},
editable: true,
});DisplayFieldOptions
| Option | Type | Default | Description |
|---|---|---|---|
label | string | Schema field label | Custom label overriding the field's schema label. |
hideLabel | boolean | false | When true, hides the field label entirely and renders only the value. |
tooltip | string | undefined | Informational tooltip shown next to the label. |
span | 1–12 | Row fraction | Grid column span across the 12-column grid. |
display | DisplayVariant | Field type fallback | Specific visual representation (see Display Variants below). |
format | string | undefined | Preset format: 'currency', 'date', 'datetime', 'relative', 'number', 'percent'. |
currency | string | 'USD' | ISO currency code (e.g., 'USD', 'EUR', 'GBP') when display: 'currency'. |
badgeColors | Record<string, string> | undefined | Color mapping for badge values. Supports named palettes (emerald, amber, rose, blue, etc.), hex codes (#10b981), Tailwind classes, and wildcards (*). |
editable | boolean | false | When true, enables an inline edit button directly in the detail view with save/cancel controls. |
keyLabel | string | 'Key' | Column header for object keys when display: 'key-value'. |
valueLabel | string | 'Value' | Column header for object values when display: 'key-value'. |
emptyText | string | '-' | Custom placeholder text when the field value is null, undefined, or empty. |
hideIfEmpty | boolean | false | When true, hides this entire field item if the value is null, empty string, or empty array. |
visible | boolean | string | true | Visibility condition. Can be a boolean or a JEXL expression evaluated against { doc, user }. |
aspectRatio | string | undefined | Aspect ratio constraint for media fields ('16/9', '4/3', '1/1'). |
objectFit | string | undefined | CSS object-fit behavior for media fields ('cover', 'contain', 'fill', 'scale-down'). |
align | 'left' | 'center' | 'right' | 'left' | Alignment of media or value within the container. |
width | number | string | undefined | Fixed width for media preview. |
height | number | string | undefined | Fixed height for media preview. |
Supported display Variants:
- Text & Code:
'text','copyable'(adds one-click copy button),'code'(fenced syntax block),'code-badge'(inline monospace pill). - Badges & Tags:
'badge'(colored status badge),'badges'/'tags'(array of badge pills). - Numbers & Metrics:
'currency','percent','progress'(visual percentage bar),'star'/'star-rating'(visual rating stars). - Dates & Times:
'date','datetime','time','relative'(e.g. "3 days ago"). - Links & Communication:
'link'/'url'(clickable external link),'email'(mailto:link),'phone'(tel:link). - Media & Colors:
'image','avatar'(circular photo),'color'(swatch + hex),'color-swatches'(palette array),'icon'(Lucide icon). - Structured Data:
'key-value'(2-column table for JSON/key-value objects),'json'(syntax formatted object preview).
2. displaySection(title, items, options?)
Groups fields and sub-components into a structured card container with a header, icon, and optional badge.
import { displaySection, displayField } from "@dyrected/core";
displaySection("Inventory & Pricing", [
displayField("sku", { span: 4, display: "copyable" }),
displayField("price", { span: 4, display: "currency" }),
displayField("stock", { span: 4, editable: true }),
], {
icon: "Package",
badge: "In Stock",
badgeColor: "emerald",
collapsible: true,
columns: 12,
});DetailSectionOptions
| Option | Type | Default | Description |
|---|---|---|---|
icon | string | undefined | Lucide icon name displayed in the section header (e.g. 'Package', 'User', 'Settings'). |
badge | string | undefined | Text badge displayed beside the section title. |
badgeColor | string | undefined | Color palette name or hex code for the section badge. |
description | string | undefined | Subtitle description displayed beneath the section title. |
span | 1–12 | 12 | Grid width on the main layout. |
columns | number | 12 | Internal grid column count for child items. |
collapsible | boolean | false | When true, allows users to collapse and expand the section. |
collapsedByDefault | boolean | false | Starts the section in collapsed state when collapsible is enabled. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
3. displayTabs(tabs, options?) & displayTab(label, items, options?)
Creates a tabbed navigation container to organize deep or multi-faceted records without cluttering the screen.
import { displayTabs, displayTab, displayField, displayRepeat } from "@dyrected/core";
displayTabs([
displayTab("Overview", [
displayField("description"),
displayField("features"),
], { icon: "FileText" }),
displayTab("Orders", [
displayRepeat("orders", [
displayField("orderNumber"),
displayField("total", { display: "currency" }),
], { layout: "table" }),
], { icon: "ShoppingCart", badge: "12", badgeColor: "blue" }),
], { span: 12, defaultTab: "Overview" });DetailTabsOptions
| Option | Type | Default | Description |
|---|---|---|---|
span | 1–12 | 12 | Width of the tabs container. |
defaultTab | string | First tab label | Label of the tab that should be active on initial render. |
visible | boolean | string | true | Visibility condition for the entire tabs container. |
DetailTabOptions
| Option | Type | Default | Description |
|---|---|---|---|
icon | string | undefined | Lucide icon name displayed inside the tab trigger. |
badge | string | undefined | Counter or status badge rendered next to the tab label (e.g., '5' or 'New'). |
badgeColor | string | undefined | Color palette name or hex code for the tab badge. |
visible | boolean | string | true | Visibility condition for this specific tab. |
4. displayGrid(columns, items, options?)
Creates a balanced multi-column sub-grid inside sections or tabs.
import { displayGrid, displayField } from "@dyrected/core";
displayGrid(3, [
displayField("city"),
displayField("state"),
displayField("postalCode"),
], { span: 12 });DetailGridOptions
| Option | Type | Default | Description |
|---|---|---|---|
span | 1–12 | 12 | Grid width of the sub-grid container in the surrounding layout. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
5. displayRepeat(fieldName, items, options?)
Renders array fields, line items, or sub-documents using 'table', 'cards', or 'list' layouts.
import { displayRepeat, displayField } from "@dyrected/core";
displayRepeat("orderItems", [
displayField("productName", { span: 6 }),
displayField("quantity", { span: 3 }),
displayField("unitPrice", { span: 3, display: "currency" }),
], {
layout: "cards",
columns: 3,
useAsTitle: "productName",
emptyText: "No items in this order",
});DetailRepeatOptions
| Option | Type | Default | Description |
|---|---|---|---|
layout | 'table' | 'cards' | 'list' | 'table' | Layout style: compact table, grid cards, or vertical list. |
columns | 1 | 2 | 3 | 4 | 3 | Column count when layout: 'cards'. |
useAsTitle / titleField | string | undefined | Field name in each row to use as the card header title (e.g. 'productName'). |
title | string | undefined | Static prefix or template for card headers (e.g. 'Item #{index}'). |
icon | string | undefined | Icon displayed in repeated card headers. |
emptyText | string | '-' | Message shown when the array is empty. |
span | 1–12 | 12 | Width of the repeated container. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
6. displayComputed(label, expressionOrHandler, options?)
Calculates live metrics, summaries, or KPI cards derived from { doc, user, db } using JEXL expressions or async functions.
import { displayComputed } from "@dyrected/core";
// JEXL string expression
displayComputed("Estimated Reading Time", 'math.ceil(doc.wordCount / 200) + " min"', {
span: 4,
});
// Function handler with currency formatting
displayComputed("Grand Total", ({ doc }) => doc.subtotal + doc.tax, {
span: 4,
format: "currency",
currency: "USD",
});DetailComputedOptions
| Option | Type | Default | Description |
|---|---|---|---|
id | string | Slugified label | Unique identifier for the computed property. |
expression | string | undefined | JEXL evaluation string (e.g. 'doc.price * doc.quantity'). |
handler | ComputedHandler | undefined | Function receiving { doc, user, db } returning a value or Promise. |
format | string | undefined | Formatting preset ('currency', 'percent', 'number', 'date'). |
currency | string | 'USD' | Currency code when format: 'currency'. |
span | 1–12 | 12 | Grid width for the KPI card. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
7. displayText(content, options?)
Renders descriptive copy, instructions, section headings, or warning callouts.
import { displayText } from "@dyrected/core";
displayText("Changes to this customer tier will affect monthly billing immediately.", {
variant: "warning",
span: 12,
});DetailTextOptions
| Option | Type | Default | Description |
|---|---|---|---|
variant | string | 'body' | Typography and alert styling variant ('body', 'heading', 'subheading', 'muted', 'caption', 'callout', 'info', 'warning'). |
className | string | undefined | Custom CSS / Tailwind classes applied to the text wrapper. |
span | 1–12 | 12 | Grid width in the 12-column layout. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
8. displayDivider(options?)
Adds horizontal divider lines to visually separate content blocks.
import { displayDivider } from "@dyrected/core";
displayDivider({ spacing: "lg" });DetailDividerOptions
| Option | Type | Default | Description |
|---|---|---|---|
spacing | 'none' | 'sm' | 'md' | 'lg' | 'md' | Vertical margin spacing around the divider line. |
span | 1–12 | 12 | Grid span in the surrounding layout. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
9. displayCustom(name, options?) / displayCustomComponent(name, options?)
Mounts a custom React component registered into the Admin UI via component slots.
import { displayCustom } from "@dyrected/core";
displayCustom("RevenueChart", {
props: { timeframe: "30d", showLegend: true },
span: 12,
});DetailCustomOptions
| Option | Type | Default | Description |
|---|---|---|---|
props | Record<string, any> | undefined | Static or initial props passed into the custom component. |
render | (ctx) => ReactNode | undefined | Inline render function receiving { doc, user } (Self-hosted React runtimes). |
span | 1–12 | 12 | Grid span in the 12-column layout. |
visible | boolean | string | true | Visibility condition (boolean or JEXL expression). |
Generated reference
The contracts below are generated from the public @dyrected/core exports by @dyrected/knowledge.
ComputedHandler
Async or synchronous handler function calculating a computed metric.
export type ComputedHandler<TDoc = any> = (context: { doc: TDoc; user?: any; db?: any }) => any | Promise<any>;DetailComputed
Computed KPI or metric card in a Detail View.
export interface DetailComputed<TDoc = any> {
/** Identifies this item as a computed value. */
type: "computed";
/** Unique computed identifier. */
id?: string;
/** Human-readable card label. */
label: string;
/** JEXL evaluation string. */
expression?: string;
/** Computed function handler. */
handler?: ComputedHandler<TDoc>;
/** Computed options. */
options?: DetailComputedOptions<TDoc>;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a computed value. |
id (optional) | Unique computed identifier. |
label (required) | Human-readable card label. |
expression (optional) | JEXL evaluation string. |
handler (optional) | Computed function handler. |
options (optional) | Computed options. |
DetailComputedOptions
Configuration options for a computed value card.
export interface DetailComputedOptions<TDoc = any> {
/** Unique identifier for the computed metric. */
id?: string;
/** JEXL expression string evaluated against `{ doc, user }`. */
expression?: string;
/** Async or sync function receiving `{ doc, user, db }` returning the computed value. */
handler?: ComputedHandler<TDoc>;
/** Grid width for the computed card. */
span?: DetailSpan;
/** Formatting preset for the computed value ('currency', 'percent', 'number', 'date'). */
format?: string;
/** Currency code when format is 'currency'. */
currency?: string;
/**
* Visibility condition for this computed card.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
}| Option | Description |
|---|---|
id (optional) | Unique identifier for the computed metric. |
expression (optional) | JEXL expression string evaluated against `{ doc, user }`. |
handler (optional) | Async or sync function receiving `{ doc, user, db }` returning the computed value. |
span (optional) | Grid width for the computed card. |
format (optional) | Formatting preset for the computed value ('currency', 'percent', 'number', 'date'). |
currency (optional) | Currency code when format is 'currency'. |
visible (optional) | Visibility condition for this computed card. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
DetailCustom
Custom React component slot mounted in a Detail View.
export interface DetailCustom<TDoc = any> {
/** Identifies this item as a custom component. */
type: "custom";
/** Registered custom component name. */
name: string;
/** Custom component options. */
options?: DetailCustomOptions<TDoc>;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a custom component. |
name (required) | Registered custom component name. |
options (optional) | Custom component options. |
DetailCustomOptions
Configuration options for a custom React component slot.
export interface DetailCustomOptions<TDoc = any> {
/** Grid span in the 12-column layout. */
span?: DetailSpan;
/**
* Visibility condition for this custom component.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
/** Static or initial props passed into the custom component. */
props?: Record<string, any>;
/** Inline render function receiving `{ doc, user }` (Self-hosted React runtimes). */
render?: (context: { doc: TDoc; user?: any; [key: string]: any }) => any;
}| Option | Description |
|---|---|
span (optional) | Grid span in the 12-column layout. |
visible (optional) | Visibility condition for this custom component. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
props (optional) | Static or initial props passed into the custom component. |
render (optional) | Inline render function receiving `{ doc, user }` (Self-hosted React runtimes). |
DetailDivider
Horizontal divider line in a Detail View layout.
export interface DetailDivider {
/** Identifies this item as a divider. */
type: "divider";
/** Divider options. */
options?: DetailDividerOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a divider. |
options (optional) | Divider options. |
DetailDividerOptions
Configuration options for a horizontal divider line.
export interface DetailDividerOptions {
/** Grid span in the surrounding layout. */
span?: DetailSpan;
/**
* Visibility condition for this divider.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
/** Vertical margin spacing around the divider line. */
spacing?: "sm" | "md" | "lg" | "none";
}| Option | Description |
|---|---|
span (optional) | Grid span in the surrounding layout. |
visible (optional) | Visibility condition for this divider. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
spacing (optional) | Vertical margin spacing around the divider line. |
DetailField
Declared field item in a Detail View.
export interface DetailField {
/** Identifies this item as a field. */
type: "field";
/** Field name or nested path in the document. */
field: string;
/** Display options for this field. */
options?: DisplayFieldOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a field. |
field (required) | Field name or nested path in the document. |
options (optional) | Display options for this field. |
DetailGrid
Multi-column sub-grid layout inside a section or tab.
export interface DetailGrid {
/** Identifies this item as a grid. */
type: "grid";
/** Number of balanced columns in this grid. */
columns: number;
/** Child items rendered across the grid columns. */
items: DetailItem[];
/** Grid container options. */
options?: DetailGridOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a grid. |
columns (required) | Number of balanced columns in this grid. |
items (required) | Child items rendered across the grid columns. |
options (optional) | Grid container options. |
DetailGridOptions
Configuration options for a sub-grid container.
export interface DetailGridOptions {
/** Grid width of the sub-grid container in the surrounding layout. */
span?: DetailSpan;
/**
* Visibility condition for this grid container.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
}| Option | Description |
|---|---|
span (optional) | Grid width of the sub-grid container in the surrounding layout. |
visible (optional) | Visibility condition for this grid container. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
DetailItem
Union of all valid item types in a Detail View schema.
export type DetailItem<TDoc = any> =
| DetailSection
| DetailTabs
| DetailGrid
| DetailField
| DetailRepeat
| DetailComputed<TDoc>
| DetailDivider
| DetailText
| DetailCustom<TDoc>
| string;DetailRepeat
Delegated repeated array field item in a Detail View.
export interface DetailRepeat {
/** Identifies this item as a repeat container. */
type: "repeat";
/** Array field name in the document. */
field: string;
/** Item template schemas rendered for each element in the array. */
items: DetailItem[];
/** Repeat options. */
options?: DetailRepeatOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a repeat container. |
field (required) | Array field name in the document. |
items (required) | Item template schemas rendered for each element in the array. |
options (optional) | Repeat options. |
DetailRepeatOptions
Configuration options for a repeated array field.
export interface DetailRepeatOptions {
/** Layout style for repeated items: 'table', 'cards', or 'list'. */
layout?: "table" | "cards" | "list";
/** Message shown when the repeated array is empty. */
emptyText?: string;
/** Grid width of the repeated container. */
span?: DetailSpan;
/**
* Visibility condition for this repeat container.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
/** Field name in each row to use as the card header title (e.g. 'title', 'key', 'name'). */
useAsTitle?: string;
/** Field name in each row to use as the card header title (alias for useAsTitle). */
titleField?: string;
/** Static card title prefix or template (e.g. 'Category' or 'Card #{index}'). */
title?: string;
/** Optional icon name for card header. */
icon?: string;
/** Number of grid columns for cards layout (1, 2, 3, or 4; defaults to 3). */
columns?: 1 | 2 | 3 | 4;
}| Option | Description |
|---|---|
layout (optional) | Layout style for repeated items: 'table', 'cards', or 'list'. |
emptyText (optional) | Message shown when the repeated array is empty. |
span (optional) | Grid width of the repeated container. |
visible (optional) | Visibility condition for this repeat container. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
useAsTitle (optional) | Field name in each row to use as the card header title (e.g. 'title', 'key', 'name'). |
titleField (optional) | Field name in each row to use as the card header title (alias for useAsTitle). |
title (optional) | Static card title prefix or template (e.g. 'Category' or 'Card #{index}'). |
icon (optional) | Optional icon name for card header. |
columns (optional) | Number of grid columns for cards layout (1, 2, 3, or 4; defaults to 3). |
DetailSchema
Detail View layout schema definition for a collection or global.
export type DetailSchema<TDoc = any> = DetailItem<TDoc>[];DetailSection
Section container grouping fields and components in a Detail View.
export interface DetailSection {
/** Identifies this item as a section. */
type: "section";
/** Section heading title. */
title: string;
/** Child items rendered inside this section. */
items: DetailItem[];
/** Section container options. */
options?: DetailSectionOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a section. |
title (required) | Section heading title. |
items (required) | Child items rendered inside this section. |
options (optional) | Section container options. |
DetailSectionOptions
Configuration options for a Detail View section container.
export interface DetailSectionOptions {
/** Lucide icon name displayed in the section header. */
icon?: string;
/** Status or counter badge displayed beside the section title. */
badge?: string;
/** Color palette name or hex code for the section badge. */
badgeColor?: string;
/** Subtitle description displayed beneath the section title. */
description?: string;
/** Grid width of the section on the main 12-column layout. */
span?: DetailSpan;
/** Internal grid column count for child items. */
columns?: number;
/** When true, allows editors to collapse and expand the section. */
collapsible?: boolean;
/** Starts the section in collapsed state when collapsible is enabled. */
collapsedByDefault?: boolean;
/**
* Visibility condition for this section.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*
* @example `visible: "user.roles != null and 'admin' in user.roles"`
*/
visible?: string | boolean;
}| Option | Description |
|---|---|
icon (optional) | Lucide icon name displayed in the section header. |
badge (optional) | Status or counter badge displayed beside the section title. |
badgeColor (optional) | Color palette name or hex code for the section badge. |
description (optional) | Subtitle description displayed beneath the section title. |
span (optional) | Grid width of the section on the main 12-column layout. |
columns (optional) | Internal grid column count for child items. |
collapsible (optional) | When true, allows editors to collapse and expand the section. |
collapsedByDefault (optional) | Starts the section in collapsed state when collapsible is enabled. |
visible (optional) | Visibility condition for this section. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
DetailSpan
Column span in the 12-column Detail View layout grid (1 to 12).
export type DetailSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;DetailTab
Single tab entry inside a tabbed container.
export interface DetailTab {
/** Identifies this item as a tab. */
type: "tab";
/** Tab label shown in the tab bar. */
label: string;
/** Child items rendered when this tab is active. */
items: DetailItem[];
/** Tab options. */
options?: DetailTabOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a tab. |
label (required) | Tab label shown in the tab bar. |
items (required) | Child items rendered when this tab is active. |
options (optional) | Tab options. |
DetailTabOptions
Configuration options for an individual tab in displayTabs().
export interface DetailTabOptions {
/** Lucide icon name displayed inside the tab trigger. */
icon?: string;
/** Counter or status badge rendered next to the tab label. */
badge?: string;
/** Color palette name or hex code for the tab badge. */
badgeColor?: string;
/**
* Visibility condition for this individual tab.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
}| Option | Description |
|---|---|
icon (optional) | Lucide icon name displayed inside the tab trigger. |
badge (optional) | Counter or status badge rendered next to the tab label. |
badgeColor (optional) | Color palette name or hex code for the tab badge. |
visible (optional) | Visibility condition for this individual tab. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
DetailTabs
Tabbed navigation container in a Detail View layout.
export interface DetailTabs {
/** Identifies this item as a tab container. */
type: "tabs";
/** List of tabs in this container. */
tabs: DetailTab[];
/** Tab container options. */
options?: DetailTabsOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a tab container. |
tabs (required) | List of tabs in this container. |
options (optional) | Tab container options. |
DetailTabsOptions
Configuration options for a tabbed container.
export interface DetailTabsOptions {
/** Grid width of the tabs container on the main layout. */
span?: DetailSpan;
/** Label of the tab that should be active on initial render. */
defaultTab?: string;
/**
* Visibility condition for this tab container.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
}| Option | Description |
|---|---|
span (optional) | Grid width of the tabs container on the main layout. |
defaultTab (optional) | Label of the tab that should be active on initial render. |
visible (optional) | Visibility condition for this tab container. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
DetailText
Static text, heading, callout, or notice block in a Detail View.
export interface DetailText {
/** Identifies this item as a text block. */
type: "text";
/** Text or markdown content. */
content: string;
/** Text options. */
options?: DetailTextOptions;
}| Option | Description |
|---|---|
type (required) | Identifies this item as a text block. |
content (required) | Text or markdown content. |
options (optional) | Text options. |
DetailTextOptions
Configuration options for a static text or callout block.
export interface DetailTextOptions {
/** Grid width in the 12-column layout. */
span?: DetailSpan;
/**
* Visibility condition for this text block.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
*/
visible?: string | boolean;
/** Typography and alert styling variant ('body', 'heading', 'subheading', 'muted', 'caption', 'callout', 'info', 'warning'). */
variant?: "body" | "heading" | "subheading" | "muted" | "caption" | "callout" | "info" | "warning";
/** Custom CSS or Tailwind classes applied to the text wrapper. */
className?: string;
}| Option | Description |
|---|---|
span (optional) | Grid width in the 12-column layout. |
visible (optional) | Visibility condition for this text block. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. |
variant (optional) | Typography and alert styling variant ('body', 'heading', 'subheading', 'muted', 'caption', 'callout', 'info', 'warning'). |
className (optional) | Custom CSS or Tailwind classes applied to the text wrapper. |
displayComputed
Renders a computed value calculated from document and user context.
export function displayComputed<TDoc = any>(
label: string,
expressionOrOptionsOrHandler: string | DetailComputedOptions<TDoc> | ComputedHandler<TDoc>,
options?: DetailComputedOptions<TDoc>,
): DetailComputed<TDoc>displayCustom
Renders a custom React component registered by name in the Admin panel.
export function displayCustom<TDoc = any>(name: string, options?: DetailCustomOptions<TDoc>): DetailCustom<TDoc>displayCustomComponent
Alias for displayCustom to render a custom component in a Detail View layout.
export function displayCustomComponent<TDoc = any>(
name: string,
options?: DetailCustomOptions<TDoc>,
): DetailCustom<TDoc>displayDivider
Creates a horizontal divider line in a Detail View layout.
export function displayDivider(options?: DetailDividerOptions): DetailDividerdisplayField
Declares a field for presentation in a Detail View.
export function displayField(fieldName: string, options?: DisplayFieldOptions): DetailFieldDisplayFieldOptions
Configuration options for rendering a field in a Detail View.
export interface DisplayFieldOptions {
/** Custom label overriding the field's schema label. */
label?: string;
/** When true, hides the field label entirely and renders only the value. */
hideLabel?: boolean;
/** Informational tooltip shown next to the label. */
tooltip?: string;
/** Grid column span across the 12-column grid. */
span?: DetailSpan;
/** Specific visual representation variant (e.g. 'badge', 'currency', 'star', 'copyable'). */
display?: DisplayVariant;
/** Preset format for numbers, dates, and currencies ('currency', 'date', 'datetime', 'relative', 'number', 'percent'). */
format?: "currency" | "date" | "datetime" | "relative" | "number" | "percent" | string;
/** ISO currency code (e.g. 'USD', 'EUR') when display or format is 'currency'. */
currency?: string;
/** Color mapping for badge and tag values (named palette, hex color, Tailwind class, or wildcard). */
badgeColors?: Record<string, string>;
/** Column header for object keys when display is 'key-value'. */
keyLabel?: string;
/** Column header for object values when display is 'key-value'. */
valueLabel?: string;
/** Custom placeholder text when the field value is null, undefined, or empty. */
emptyText?: string;
/** When true, hides this entire field item if the value is null or empty. */
hideIfEmpty?: boolean;
/**
* When true, allows inline editing of this field in the Detail View
* via an interactive toggle.
*/
editable?: boolean;
/**
* Visibility condition for this field item.
* Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
* When false or evaluating to falsy, this item is hidden in the Detail View.
*
* @example `visible: "doc.status == 'published'"`
* @example `visible: false`
*/
visible?: string | boolean;
/** Specific aspect ratio constraint for media (e.g., "16/9", "4/3", "1/1", "auto"). */
aspectRatio?: string;
/** CSS object-fit behavior for media ('cover', 'contain', 'fill', 'scale-down'). */
objectFit?: "cover" | "contain" | "fill" | "scale-down";
/** Media alignment ('left', 'center', 'right'). */
align?: "left" | "center" | "right";
/** Fixed pixel width or CSS dimension for media preview. */
width?: string | number;
/** Fixed pixel height or CSS dimension for media preview. */
height?: string | number;
}| Option | Description |
|---|---|
label (optional) | Custom label overriding the field's schema label. |
hideLabel (optional) | When true, hides the field label entirely and renders only the value. |
tooltip (optional) | Informational tooltip shown next to the label. |
span (optional) | Grid column span across the 12-column grid. |
display (optional) | Specific visual representation variant (e.g. 'badge', 'currency', 'star', 'copyable'). |
format (optional) | Preset format for numbers, dates, and currencies ('currency', 'date', 'datetime', 'relative', 'number', 'percent'). |
currency (optional) | ISO currency code (e.g. 'USD', 'EUR') when display or format is 'currency'. |
badgeColors (optional) | Color mapping for badge and tag values (named palette, hex color, Tailwind class, or wildcard). |
keyLabel (optional) | Column header for object keys when display is 'key-value'. |
valueLabel (optional) | Column header for object values when display is 'key-value'. |
emptyText (optional) | Custom placeholder text when the field value is null, undefined, or empty. |
hideIfEmpty (optional) | When true, hides this entire field item if the value is null or empty. |
editable (optional) | When true, allows inline editing of this field in the Detail View via an interactive toggle. |
visible (optional) | Visibility condition for this field item. Can be a boolean or a JEXL expression evaluated against `{ doc, user }`. When false or evaluating to falsy, this item is hidden in the Detail View. |
aspectRatio (optional) | Specific aspect ratio constraint for media (e.g., "16/9", "4/3", "1/1", "auto"). |
objectFit (optional) | CSS object-fit behavior for media ('cover', 'contain', 'fill', 'scale-down'). |
align (optional) | Media alignment ('left', 'center', 'right'). |
width (optional) | Fixed pixel width or CSS dimension for media preview. |
height (optional) | Fixed pixel height or CSS dimension for media preview. |
displayGrid
Creates a multi-column grid layout inside a section or detail schema.
export function displayGrid(columns: number, items: DetailItem[], options?: DetailGridOptions): DetailGriddisplayRepeat
Renders an array/repeated field using a delegated display schema.
Supports 'table', 'cards', and 'list' layouts.
export function displayRepeat(fieldName: string, items: DetailItem[], options?: DetailRepeatOptions): DetailRepeatdisplaySection
Creates a section container in a Detail View layout.
export function displaySection(title: string, items: DetailItem[], options?: DetailSectionOptions): DetailSectiondisplayTab
Creates a single tab entry for use within displayTabs().
export function displayTab(label: string, items: DetailItem[], options?: DetailTabOptions): DetailTabdisplayTabs
Creates a tabbed container in a Detail View layout.
export function displayTabs(tabs: DetailTab[], options?: DetailTabsOptions): DetailTabsdisplayText
Renders static text, heading, callout, or information notice in a Detail View layout.
export function displayText(content: string, options?: DetailTextOptions): DetailTextDisplayVariant
Visual display variants supported by DetailFieldRenderer.
export type DisplayVariant =
| "text"
| "badge"
| "code-badge"
| "code"
| "copyable"
| "link"
| "url"
| "email"
| "phone"
| "currency"
| "percent"
| "progress"
| "star"
| "star-rating"
| "boolean"
| "date"
| "datetime"
| "time"
| "relative"
| "image"
| "avatar"
| "color"
| "color-swatches"
| "icon"
| "key-value"
| "table"
| "tags"
| "badges"
| "json";evaluateDetailComputed
Evaluates server-side computed functions and JEXL expressions from a detail schema
and attaches them to doc._meta.computed.
export async function evaluateDetailComputed(
detail: DetailSchema | boolean | undefined,
doc: any,
user: any,
db: any,
): Promise<any>generateDefaultDetailSchema
Generates an automatic default 12-column Detail View schema for a collection or global
when no explicit detail configuration is provided.
export function generateDefaultDetailSchema(schema: {
fields?: Field[];
labels?: { singular?: string; plural?: string };
label?: string;
slug?: string;
}): DetailSchemaisDetailItemVisible
Evaluates whether a detail item should be visible based on its options.visible rule.
Supports boolean values or JEXL expressions evaluated against { doc, user }.
Works consistently for top-level and nested detail items.
export function isDetailItemVisible(item: DetailItem, doc: any, user?: any): booleannormalizeDetailItem
Normalizes a DetailItem (which may be a shorthand string) into a standard DetailItem object.
export function normalizeDetailItem(item: DetailItem): Exclude<DetailItem, string>