HotCRM Logo

Page Components Reference

Guide to all component types available in HotCRM page layouts — record, page, and AI components with property reference.

Page Components Reference

Page layouts define how record detail pages, home pages, and app pages are rendered. Every *.page.ts file exports a Page validated against PageSchema from @objectstack/spec/ui. This guide covers all available component types and their properties.

File convention: packages/{package}/src/{object}.page.ts

Schema Overview

import type { Page } from '@objectstack/spec/ui';
import { PageSchema } from '@objectstack/spec/ui';

export const MyPage = {
  name: 'my_object_detail',
  object: 'my_object',
  type: 'record',
  label: 'My Object Detail Page',
  template: 'record_detail',
  isDefault: true,
  assignedProfiles: ['admin', 'standard_user'],

  regions: [
    {
      name: 'header',
      components: [ /* ... */ ]
    }
  ]
} satisfies Page;

PageSchema.parse(MyPage);
export default MyPage;

Page Types

TypeDescriptionUse Case
recordDetail page for a single recordAccount, Opportunity, Case detail views
homeLanding page for a module or appSales Home, Service Console
appApplication-level pageAdmin settings, configuration pages
utilityUtility or tool pageCalculators, data import wizards

Page-Level Properties

PropertyTypeDescription
namestringUnique identifier for the page (snake_case).
objectstringThe business object this page is associated with.
typestringPage type: record, home, app, or utility.
labelstringHuman-readable page title.
templatestringBase template to extend (e.g., 'record_detail').
isDefaultbooleanWhether this is the default page for the object.
assignedProfilesstring[]Security profiles that can access this page.
regionsarrayArray of regions containing components.

Component Reference

Components are organized into regions. Each component has a type, optional label, optional visibility expression, and a properties object.

Page Components

Top-of-page header area for branding, navigation, or contextual information.

Bottom-of-page footer for actions, status information, or navigation links.

page:sidebar

Side panel region for supplementary content like AI assistants or quick actions.

page:tabs

Horizontal tab navigation that organizes content into switchable panes. Each tab name corresponds to a region in the page.

{
  type: 'page:tabs' as const,
  properties: {
    tabs: ['account_info', 'address_info', 'additional_info']
  }
}

PageTabsProps:

PropertyTypeDescription
tabsstring[]Array of region names to render as tabs.

page:accordion

Collapsible sections stacked vertically. Each section can be expanded or collapsed independently.

page:card

A bordered content card for grouping related information within a region.

page:section

A labeled section divider for organizing content within a region.


Record Components

record:details

Displays a set of fields for the current record in a columnar grid layout.

{
  type: 'record:details' as const,
  label: 'Account Information',
  properties: {
    fields: [
      'name', 'account_number', 'type', 'industry',
      'phone', 'website', 'annual_revenue', 'number_of_employees',
      'parent_account', 'ownership'
    ],
    columns: 2
  }
}

RecordDetailsProps:

PropertyTypeDescription
fieldsstring[]Field API names to display.
columnsnumberNumber of columns in the grid layout (typically 1 or 2).

record:highlights

A prominent header strip showing key fields for at-a-glance identification of the record.

{
  type: 'record:highlights' as const,
  properties: {
    fields: ['name', 'type', 'industry', 'annual_revenue', 'phone']
  }
}

RecordHighlightsProps:

PropertyTypeDescription
fieldsstring[]Key fields to display in the highlights bar (typically 4–6 fields).

record:related_list

Displays a table of related child records with filtering, sorting, and inline actions.

{
  type: 'record:related_list' as const,
  label: 'Opportunities',
  visibility: "record.type == 'Customer'",
  properties: {
    object: 'opportunity',
    columns: ['name', 'stage', 'amount', 'close_date', 'probability'],
    filters: [['stage', '!=', 'closed_lost']],
    sort: [{ field: 'close_date', direction: 'asc' }],
    actions: ['new', 'edit', 'delete']
  }
}

RecordRelatedListProps:

PropertyTypeDescription
objectstringAPI name of the related object.
columnsstring[]Fields to display as table columns.
filtersarrayObjectQL-style filter conditions.
sortarraySort order: { field, direction }.
actionsstring[]Available actions: 'new', 'edit', 'delete'.

record:activity

A timeline of activities (calls, emails, meetings, tasks) associated with the record.

{
  type: 'record:activity' as const,
  label: 'Activity Timeline',
  properties: {}
}

record:chatter

A collaboration feed for posting comments, mentions, and file attachments on the record.

record:path

A visual progress indicator showing the record's position in a stage-based process (e.g., Opportunity stages).


AI Components

ai:chat_window

An embedded AI assistant for context-aware help, record analysis, and natural language queries.

{
  type: 'ai:chat_window' as const,
  label: 'Account Intelligence',
  properties: {
    mode: 'sidebar'
  }
}
PropertyTypeDescription
modestringDisplay mode: 'sidebar' for a side panel, or 'inline' for embedded within the page.

ai:suggestion

Displays AI-generated suggestions or recommendations contextual to the current record.


Complete Page Example

The Account detail page demonstrates how regions and components work together:

// packages/crm/src/account.page.ts
import type { Page } from '@objectstack/spec/ui';
import { PageSchema } from '@objectstack/spec/ui';

export const AccountPage = {
  name: 'account_detail',
  object: 'account',
  type: 'record' as const,
  label: 'Account Detail Page',
  template: 'record_detail',
  isDefault: true,
  assignedProfiles: ['sales_rep', 'sales_manager', 'account_executive', 'admin'],

  regions: [
    {
      name: 'header',
      components: [
        {
          type: 'record:highlights' as const,
          properties: {
            fields: ['name', 'type', 'industry', 'annual_revenue', 'phone']
          }
        }
      ]
    },
    {
      name: 'tabs',
      components: [
        {
          type: 'page:tabs' as const,
          properties: {
            tabs: ['account_info', 'address_info', 'additional_info']
          }
        }
      ]
    },
    {
      name: 'account_info',
      components: [
        {
          type: 'record:details' as const,
          label: 'Account Information',
          properties: {
            fields: [
              'name', 'account_number', 'type', 'industry',
              'phone', 'website', 'annual_revenue', 'number_of_employees',
              'parent_account', 'ownership'
            ],
            columns: 2
          }
        }
      ]
    },
    {
      name: 'related_lists',
      components: [
        {
          type: 'record:related_list' as const,
          label: 'Opportunities',
          properties: {
            object: 'opportunity',
            columns: ['name', 'stage', 'amount', 'close_date', 'probability'],
            filters: [['stage', '!=', 'closed_lost']],
            sort: [{ field: 'close_date', direction: 'asc' }],
            actions: ['new', 'edit', 'delete']
          }
        },
        {
          type: 'record:related_list' as const,
          label: 'Contacts',
          properties: {
            object: 'contact',
            columns: ['name', 'title', 'email', 'phone', 'is_decision_maker'],
            actions: ['new', 'edit', 'delete']
          }
        },
        {
          type: 'record:related_list' as const,
          label: 'Cases',
          visibility: "record.sla_tier != null",
          properties: {
            object: 'case',
            columns: ['case_number', 'subject', 'status', 'priority', 'created_date'],
            filters: [['status', '!=', 'closed']],
            sort: [{ field: 'created_date', direction: 'desc' }],
            actions: ['new', 'edit']
          }
        }
      ]
    },
    {
      name: 'activity',
      components: [
        {
          type: 'record:activity' as const,
          label: 'Activity Timeline',
          properties: {}
        }
      ]
    },
    {
      name: 'ai_assistant',
      components: [
        {
          type: 'ai:chat_window' as const,
          label: 'Account Intelligence',
          properties: {
            mode: 'sidebar'
          }
        }
      ]
    }
  ]
} satisfies Page;

PageSchema.parse(AccountPage);
export default AccountPage;

Component Visibility

Any component can include a visibility expression to conditionally show or hide it based on record data:

{
  type: 'record:related_list' as const,
  label: 'Cases',
  visibility: "record.sla_tier != null",
  properties: { /* ... */ }
}

The expression is evaluated at render time against the current record context.


Profile Assignment

Use assignedProfiles at the page level to control which user profiles see this layout:

assignedProfiles: ['sales_rep', 'sales_manager', 'account_executive', 'admin']

When isDefault: true, this page layout is used for all profiles unless a more specific layout is assigned.


Quick Reference

What you wantHow to do it
Key field headerrecord:highlights with 4–6 fields
Tabbed contentpage:tabs with region names as tab IDs
Field display gridrecord:details with fields and columns
Related child recordsrecord:related_list with object, columns, filters
Activity feedrecord:activity in its own region
AI assistantai:chat_window with mode: 'sidebar'
Conditional componentAdd visibility expression to any component
Profile-restricted pageSet assignedProfiles at the page level

On this page