DescriptionCard
DescriptionCard is a powerful component for displaying structured document information in ERP applications. It automatically renders field-value pairs in a responsive grid with support for multiple field types, dividers, and smart empty value handling.
Import
import { DescriptionCard } from "@tailor-platform/app-shell";Basic Usage
const orderData = {
orderNumber: "ORD-12345",
customer: "Acme Corporation",
status: "shipped",
totalAmount: 15750.0,
currency: "USD",
orderDate: "2026-03-01T10:00:00Z",
};
<DescriptionCard
data={orderData}
fields={[
{ key: "orderNumber", label: "Order Number" },
{ key: "customer", label: "Customer" },
{ key: "status", label: "Status", type: "badge" },
{ key: "totalAmount", label: "Total", type: "money", meta: { currencyKey: "currency" } },
{ key: "orderDate", label: "Order Date", type: "date" },
]}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown> | Required | Data object containing field values |
fields | FieldConfig[] | Required | Array of field configurations or dividers |
columns | 2 | 3 | 4 | 3 | Number of columns in the grid |
className | string | - | Additional CSS classes |
style | CSSProperties | - | Inline styles for the card container |
Field Types
text (default)
Plain text display with optional truncation and copy button.
{
key: "description",
label: "Description",
type: "text", // or omit - it's the default
meta: {
copyable: true,
truncateLines: 3, // Show "..." after 3 lines with tooltip
},
}badge
Displays value as a Badge component with variant mapping.
{
key: "status",
label: "Status",
type: "badge",
meta: {
sentenceCaseBadges: false, // Optional: render the original value instead
badgeVariantMap: {
draft: "neutral",
pending: "subtle-warning",
approved: "subtle-success",
shipped: "success",
cancelled: "subtle-error",
},
},
}Badge values are rendered in sentence case by default. Set meta.sentenceCaseBadges to false when you need to display the original value from your data.
money
Formats currency values with proper locale formatting.
{
key: "amount",
label: "Amount",
type: "money",
meta: {
currencyKey: "currency", // Path to currency code in data
},
}
// Data: { amount: 1234.56, currency: "USD" }
// Renders: $1,234.56date
Formats date/time values with multiple format options.
{
key: "createdAt",
label: "Created",
type: "date",
meta: {
dateFormat: "medium", // "short" | "medium" | "long" | "relative"
},
}Format examples:
short: 3/6/2026medium: Mar 6, 2026long: March 6, 2026 at 2:30 PMrelative: 2 hours ago / In 3 days
Relative dates support both past and future values and are automatically localized based on the AppShell locale (English and Japanese are built in).
link
Renders a clickable link.
{
key: "customerName",
label: "Customer",
type: "link",
meta: {
hrefKey: "customerUrl", // Path to URL in data
external: true, // Opens in new tab
},
}Internal links (when external is not set or false) use react-router <Link> for client-side navigation. External links (when external: true) open in a new tab using a standard <a> tag.
address
Formats multi-line address display.
{
key: "shippingAddress",
label: "Shipping Address",
type: "address",
}
// Data: {
// shippingAddress: {
// street: "123 Main St",
// city: "San Francisco",
// state: "CA",
// zip: "94105",
// }
// }reference
Links to related documents with auto-generated URLs using react-router <Link> for client-side navigation.
{
key: "invoiceNumber",
label: "Invoice",
type: "reference",
meta: {
referenceIdKey: "invoiceId",
referenceUrlPattern: "/invoices/{id}",
},
}Dividers
Use dividers to visually separate field groups:
<DescriptionCard
data={data}
fields={[
{ key: "orderNumber", label: "Order Number" },
{ key: "customer", label: "Customer" },
{ type: "divider" }, // Creates a horizontal line
{ key: "status", label: "Status", type: "badge" },
{ key: "totalAmount", label: "Total", type: "money" },
]}
/>Empty Value Behavior
Control what displays when a field value is empty:
{
key: "notes",
label: "Notes",
emptyBehavior: "dash", // Shows "-" when empty (default)
}
{
key: "optionalField",
label: "Optional",
emptyBehavior: "hide", // Hides the entire field when empty
}Nested Data Access
Use dot notation to access nested properties:
const data = {
customer: {
name: "Acme Corp",
contact: {
email: "contact@acme.com",
},
},
};
<DescriptionCard
data={data}
fields={[
{ key: "customer.name", label: "Customer Name" },
{ key: "customer.contact.email", label: "Email" },
]}
/>;Column Layouts
Choose between 2, 3, or 4 column layouts:
// 2 columns (better for mobile)
<DescriptionCard columns={2} data={data} fields={fields} />
// 3 columns (default, balanced)
<DescriptionCard columns={3} data={data} fields={fields} />
// 4 columns (more compact)
<DescriptionCard columns={4} data={data} fields={fields} />The component is fully responsive:
- Mobile (< 400px): 1 column
- Tablet (400-600px): 2 columns
- Desktop (600-800px): 3 columns
- Large (> 800px): 4 columns (if
columns={4})
Complete Example
import { DescriptionCard } from "@tailor-platform/app-shell";
const orderData = {
// Order info
orderNumber: "ORD-12345",
orderDate: "2026-03-01T10:00:00Z",
status: "shipped",
// Customer
customer: {
name: "Acme Corporation",
id: "CUST-001",
email: "orders@acme.com",
},
// Financial
subtotal: 14000.0,
tax: 1750.0,
total: 15750.0,
currency: "USD",
// Shipping
shippingAddress: {
street: "123 Business Ave",
city: "San Francisco",
state: "CA",
zip: "94105",
},
trackingNumber: "1Z999AA10123456784",
// Notes
notes: "Rush delivery requested",
};
function OrderDetails() {
return (
<DescriptionCard
data={orderData}
columns={3}
fields={[
// Order Information
{
key: "orderNumber",
label: "Order Number",
meta: { copyable: true },
},
{
key: "orderDate",
label: "Order Date",
type: "date",
meta: { dateFormat: "medium" },
},
{
key: "status",
label: "Status",
type: "badge",
meta: {
badgeVariantMap: {
draft: "neutral",
pending: "subtle-warning",
shipped: "subtle-success",
delivered: "success",
cancelled: "subtle-error",
},
},
},
{ type: "divider" },
// Customer Information
{
key: "customer.name",
label: "Customer",
type: "reference",
meta: {
referenceIdKey: "customer.id",
referenceUrlPattern: "/customers/{id}",
},
},
{
key: "customer.email",
label: "Email",
type: "link",
meta: { hrefKey: "customer.email" },
},
{ type: "divider" },
// Financial
{
key: "subtotal",
label: "Subtotal",
type: "money",
meta: { currencyKey: "currency" },
},
{
key: "tax",
label: "Tax",
type: "money",
meta: { currencyKey: "currency" },
},
{
key: "total",
label: "Total",
type: "money",
meta: { currencyKey: "currency" },
},
{ type: "divider" },
// Shipping
{
key: "shippingAddress",
label: "Shipping Address",
type: "address",
},
{
key: "trackingNumber",
label: "Tracking",
meta: { copyable: true },
},
{ type: "divider" },
// Notes
{
key: "notes",
label: "Notes",
meta: { truncateLines: 2 },
emptyBehavior: "hide",
},
]}
/>
);
}Styling
The component uses container queries for responsive layouts:
// Custom styling
<DescriptionCard
className="astw:bg-white astw:p-6 astw:rounded-lg astw:shadow"
data={data}
fields={fields}
/>Accessibility
- Proper semantic HTML structure
- Labels associated with values
- Keyboard accessible copy buttons
- Tooltips for truncated content
- Screen reader friendly
Best Practices
Do:
- ✅ Group related fields with dividers
- ✅ Use appropriate field types for data
- ✅ Enable
copyablefor IDs and codes - ✅ Use
emptyBehavior: "hide"for optional fields - ✅ Provide
badgeVariantMapfor status fields
Don't:
- ❌ Put too many fields (consider splitting into tabs)
- ❌ Use more than 4 columns (hard to read)
- ❌ Mix unrelated data without dividers
- ❌ Forget to handle nested data properly
TypeScript
Full type safety with TypeScript:
import { type DescriptionCardProps } from "@tailor-platform/app-shell";
// Define your data type
interface Order {
orderNumber: string;
customer: string;
status: "draft" | "pending" | "shipped";
total: number;
currency: string;
}
// Type-safe field definitions
const fields: DescriptionCardProps["fields"] = [
{ key: "orderNumber", label: "Order" },
{ key: "status", label: "Status", type: "badge" },
// ... more fields
];
<DescriptionCard<Order> data={orderData} fields={fields} />Related Components
Related Concepts
- Styling and Theming - Customize appearance