Skip to main content
The DescriptionCard component is designed for displaying structured document information like orders, invoices, products, and customer details. It provides automatic field rendering based on data types with responsive layouts.

Import

import { DescriptionCard } from "@tailor-platform/app-shell";

Basic Usage

import { DescriptionCard } from "@tailor-platform/app-shell";

const OrderDetails = ({ order }) => (
  <DescriptionCard
    data={order}
    title="Order Summary"
    fields={[
      { key: "orderNumber", label: "Order #" },
      { key: "status", label: "Status", type: "badge" },
      { key: "total", label: "Total", type: "money" },
      { key: "createdAt", label: "Created", type: "date" },
    ]}
  />
);

Props

data
Record<string, unknown>
required
Raw data object containing all field values. The component extracts values using the key paths defined in fields.
title
string
required
Card title displayed in the header section
fields
FieldConfig[]
required
Array of field configurations and dividers. Each field defines how to extract and display a value from the data object.See Field Types for available field configurations.
columns
3 | 4
default:"3"
Number of columns to display on desktop. The layout automatically responds to container width:
  • 3 columns: 1 → 2 → 3 columns (at 400px, 600px breakpoints)
  • 4 columns: 1 → 2 → 3 → 4 columns (at 400px, 600px, 800px breakpoints)
className
string
Additional CSS classes for custom styling
headerAction
React.ReactNode
Action component displayed in the card header (e.g., an Edit button)
headerAction={<Button variant="ghost">Edit</Button>}

Field Types

The DescriptionCard supports 7 specialized field types, each with specific rendering and metadata options.

Text Field (default)

Standard text display with optional copy functionality and truncation.
{
  key: "orderNumber",
  label: "Order #",
  type: "text", // optional, default
  meta: {
    copyable: true, // Show copy button
    truncateLines: 2, // Truncate after 2 lines with tooltip
  }
}

Badge Field

Displays values as colored status badges with automatic variant mapping.
{
  key: "status",
  label: "Status",
  type: "badge",
  meta: {
    badgeVariantMap: {
      CONFIRMED: "outline-success",
      PENDING: "outline-warning",
      CANCELLED: "outline-error",
    },
  },
}
Features:
  • Automatically converts values to sentence case (“CONFIRMED” → “Confirmed”)
  • Falls back to neutral variant if value not in map
  • See Badge component for available variants

Money Field

Formats currency values with proper locale formatting.
{
  key: "total",
  label: "Total",
  type: "money",
  meta: {
    currencyKey: "currency", // Path to currency code (e.g., "USD")
  },
}
Example data:
{
  "total": 1234.56,
  "currency": "USD"
}
Displays: $1,234.56

Date Field

Formats date/timestamp values with multiple format options.
{
  key: "createdAt",
  label: "Created",
  type: "date",
  meta: {
    dateFormat: "medium", // "short" | "medium" | "long" | "relative"
  },
}
Format examples:
  • short: 12/25/2023
  • medium: Dec 25, 2023
  • long: December 25, 2023
  • relative: 2 days ago
Renders clickable links with optional external icon.
{
  key: "website",
  label: "Website",
  type: "link",
  meta: {
    hrefKey: "websiteUrl", // Path to URL in data
    external: true, // Open in new tab
  },
}

Reference Field

Displays a value with a link to a related document.
{
  key: "customer.name",
  label: "Customer",
  type: "reference",
  meta: {
    referenceIdKey: "customer.id",
    referenceUrlPattern: "/customers/{id}", // {id} replaced with referenceIdKey value
  },
}

Address Field

Displays multi-line address information (always takes full width).
{
  key: "shippingAddress",
  label: "Shipping Address",
  type: "address",
}
Expects address object with:
{
  "shippingAddress": {
    "street": "123 Main St",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "country": "USA"
  }
}

Field Configuration Options

Nested Data Access

Use dot notation to access nested properties:
fields={[
  { key: "customer.name", label: "Customer Name" },
  { key: "customer.email", label: "Customer Email" },
  { key: "shipping.address.city", label: "Ship To City" },
]}

Empty Value Handling

Control how empty/null/undefined values are displayed:
{
  key: "notes",
  label: "Notes",
  emptyBehavior: "hide", // "dash" (default) | "hide"
}
  • "dash" (default): Shows ”—” for empty values
  • "hide": Completely hides the field if value is empty

Section Dividers

Add visual separators between groups of fields:
fields={[
  // Customer section
  { key: "customer.name", label: "Customer" },
  { key: "customer.email", label: "Email" },
  
  { type: "divider" }, // Horizontal line
  
  // Payment section
  { key: "paymentMethod", label: "Payment Method" },
  { key: "total", label: "Total", type: "money" },
]}

Complete Example

import { DescriptionCard } from "@tailor-platform/app-shell";
import { Button } from "@/components/ui/button";

const OrderDetailsCard = ({ order }) => (
  <DescriptionCard
    data={order}
    title="Order Summary"
    headerAction={
      <Button variant="outline" size="sm">
        Edit Order
      </Button>
    }
    columns={3}
    fields={[
      // Status and identifiers
      {
        key: "status",
        label: "Status",
        type: "badge",
        meta: {
          badgeVariantMap: {
            CONFIRMED: "outline-success",
            PENDING: "outline-warning",
            SHIPPED: "outline-info",
            CANCELLED: "outline-error",
          },
        },
      },
      {
        key: "orderNumber",
        label: "Order #",
        meta: { copyable: true },
      },
      {
        key: "createdAt",
        label: "Order Date",
        type: "date",
        meta: { dateFormat: "medium" },
      },
      
      // Customer information
      { type: "divider" },
      {
        key: "customer.name",
        label: "Customer",
        type: "reference",
        meta: {
          referenceIdKey: "customer.id",
          referenceUrlPattern: "/customers/{id}",
        },
      },
      { key: "customer.email", label: "Email" },
      { key: "customer.phone", label: "Phone" },
      
      // Financial details
      { type: "divider" },
      {
        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" },
      },
      
      // Shipping address (full width)
      { type: "divider" },
      {
        key: "shippingAddress",
        label: "Shipping Address",
        type: "address",
      },
      
      // Optional notes
      {
        key: "notes",
        label: "Notes",
        meta: { truncateLines: 3 },
        emptyBehavior: "hide",
      },
    ]}
  />
);

Responsive Behavior

The DescriptionCard uses CSS container queries to respond to its container width (not viewport width). This ensures correct behavior when placed in narrow containers or sidebars. 3 Column Mode (default):
  • Container < 400px: 1 column
  • Container 400px - 599px: 2 columns
  • Container ≥ 600px: 3 columns
4 Column Mode:
  • Container < 400px: 1 column
  • Container 400px - 599px: 2 columns
  • Container 600px - 799px: 3 columns
  • Container ≥ 800px: 4 columns

TypeScript

The component exports comprehensive TypeScript types:
import { 
  type DescriptionCardProps,
  type FieldConfig,
  type FieldDefinition,
  type FieldType,
  type FieldMeta,
} from "@tailor-platform/app-shell";

// Type-safe field configuration
const fields: FieldConfig[] = [
  {
    key: "status",
    label: "Status",
    type: "badge",
    meta: {
      badgeVariantMap: {
        CONFIRMED: "outline-success",
      },
    },
  },
];

Common Patterns

Multi-Currency Support

const fields: FieldConfig[] = [
  {
    key: "price",
    label: "Price",
    type: "money",
    meta: { currencyKey: "currency" },
  },
];

// Data example
const product = {
  price: 99.99,
  currency: "EUR", // Displays: €99.99
};

Dynamic Status Badges

const statusMap: Record<string, BadgeVariantType> = {
  DRAFT: "outline-neutral",
  PENDING_APPROVAL: "outline-warning",
  APPROVED: "outline-success",
  REJECTED: "outline-error",
};

const fields: FieldConfig[] = [
  {
    key: "status",
    label: "Status",
    type: "badge",
    meta: { badgeVariantMap: statusMap },
  },
];

Conditional Field Display

const fields: FieldConfig[] = [
  {
    key: "internalNotes",
    label: "Internal Notes",
    emptyBehavior: "hide", // Only show if there are notes
  },
];

Build docs developers (and LLMs) love