# Record Layout (LWC)

The **GM - Record Layout (LWC)** component is designed to display related **standard or custom Salesforce objects** using a configurable layout. It overcomes the limitations of the Salesforce UI API, offering a more flexible and powerful way to interact with data.

This component enables the manipulation of **any Salesforce object** standard or custom with dedicated support for **Event** and **Task** records.

**Global Key Features**

* **Multi-Column Layout**: Supports layouts with more than two columns for greater flexibility and improved visual organization.
* **Dynamic Forms**: Dynamically display fields based on the values of other fields, enabling intelligent and responsive form behavior.
* **Advanced Error Handling**: Implements Salesforce-like record and field-level error handling to ensure a consistent user experience.
* **Conditional Coloring**: Supports custom coloring rules to enhance readability and highlight key data.

**Edit Features**

* **Edit and Form Modes**: Easily switch between read-only and editable modes to accommodate different interaction needs.
* **Delegated Save**: Provides delegated save functionality for streamlined data handling and custom logic control.
* **Default Values**: Allows setting default field values for faster and more accurate record creation.

{% hint style="info" %}
Fully supports pre-save and post-save hooks for custom logic execution during the save process
{% endhint %}

## Enhanced Record Layout

### **Multi-Column Layout**&#x20;

**GridMate** bypasses Salesforce’s standard 2 columns layout limitation, allowing for more flexible and customizable record layouts.

<figure><img src="/files/fj6f3eHWfpgI4l3hnFkZ" alt=""><figcaption></figcaption></figure>

Below the the **Multi-Column** Layout  Json configuration.

{% code expandable="true" %}

```json
{
    "density": "comfy",
    "sections": [
        {
            "name": "accountInformation",
            "label": "Account Information",
            "active": true,
            "cols": 3,
            "rows": [
                [
                    {
                        "apiName": "Name"
                    },
                    {
                        "apiName": "AccountSource"
                    },
                    {
                        "apiName": "Industry"
                    }
                ],
                [
                    {
                        "apiName": "Phone"
                    },
                    {
                        "apiName": "Fax"
                    },
                    {
                        "apiName": "Website"
                    }
                ]
            ]
        },
        {
            "name": "addressInformation",
            "label": "Address Information",
            "active": true,
            "cols": 2,
            "rows": [
                [
                    {
                        "apiName": "BillingStreet"
                    },
                    {
                        "apiName": "ShippingStreet"
                    }
                ],
                [
                    {
                        "apiName": "BillingCity"
                    },
                    {
                        "apiName": "ShippingCity"
                    }
                ],
                [
                    {
                        "apiName": "BillingPostalCode"
                    },
                    {
                        "apiName": "ShippingPostalCode"
                    }
                ]
            ]
        }
    ]
}
```

{% endcode %}

### Dynamic Forms

Dynamically display fields or sections based on the values of other fields.

<figure><img src="/files/tnpb9QCgjpDsHxIqlrup" alt=""><figcaption></figcaption></figure>

Below is the **JSON configuration** used for this example:

{% tabs %}
{% tab title="Field Visibility" %}

```json
{
    "apiName": "Website",
    "visibility": {
        "Industry": {
            "operator": "=",
            "value": "Media"
        }
    }
}
```

{% endtab %}

{% tab title="Section Visibility" %}

```json
{
    "name": "addressInformation",
    "label": "Address Information",
    "active": true,
    "cols": 2,
    "visibility": {
        "Industry": {
            "operator": "!=",
            "value": "Media"
        }
    },
    "rows": [...]
}
```

{% endtab %}
{% endtabs %}

### **Advanced Error Handling**

We can handle both **field-level** and **record-level** errors. In this example, we’ve created two validation rules:

* The first rule triggers an error message at the **field level**.
* The second rule displays the error at the **record layout level**.

<figure><img src="/files/D87ZwE1x1OZphBeFUlkI" alt=""><figcaption></figcaption></figure>

Below is an example of  how it will be displayed on the UI:

* The **first validation rule** shows an error message directly on the **field** where the issue occurs.
* The **second validation rule** displays the error at the **record layout level**, indicating a broader record-related issue.

<figure><img src="/files/IyOA4NILbyDATSDCXuHA" alt=""><figcaption></figcaption></figure>

### **Conditional Coloring**

Below is an example of Conditional Coloring:\
When the **Account Source** is set to **Partner**, the **Account Site** field is automatically highlighted in **green** to visually indicate the condition, and this behavior works in both **Edit** and **Read** modes.

<figure><img src="/files/q6Cf7HsFCMF2P4sf3NrN" alt=""><figcaption></figcaption></figure>

Below is the **JSON configuration** used for this example:

```coffeescript
{
    "apiName": "Site",
    "coloring": [
        {
            "color": "#a6f79e",
            "exp": {
                "AccountSource": {
                    "operator": "=",
                    "value": "Partner"
                }
            }
        }
    ]
}
```

## Edit/Form Mode

To **enable Edit Form Mode**, the component must be wrapped inside an **Aura wrapper**.\
Below is the configuration used for this setup.

{% hint style="info" %}
When using **Edit Mode**, make sure to set the **defaultValues** property.\
Leave it empty only if you don’t have any default information to display \
Otherwise, the component will not appear.
{% endhint %}

#### **Component Managed Save**

Allow the component to handle the **save process** and **error handling** automatically.\
You can also redirect or customize any post-save actions inside the `handleSave` method

{% tabs fullWidth="true" %}
{% tab title="auraRecordLayout.cmp" %}
{% code fullWidth="true" expandable="true" %}

```xml
<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">
                
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <aura:if isTrue="{!v.isReady}">
        <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                        sObjectName="{!v.sObjectName}"
                                        editMode="true"
                                       
                                        recordId="{!v.recordId}"
                                        recordIdField="recordId"
                                        targetObjName="Account"
                                        
                                        recordLayout="{!v.recordLayoutJSON}"
                                        
                                        defaultValues="{!v.defaultValues}" 
                                        
                                        onsuccess="{!c.handleSuccess}"
                                        />
    </aura:if>
</aura:component>
```

{% endcode %}
{% endtab %}

{% tab title="auraRecordLayout.js" %}
{% code expandable="true" %}

```javascript
({
    doInit : function(component, event, helper) { 
        const recordLayoutJSON = {
            "density": "comfy",
            "sections": [
                {
                    "name": "accountInformation",
                    "label": "Account Information",
                    "active": true,
                    "cols": 3,
                    "rows": [
                        [
                            {
                                "apiName": "Name"
                            },
                            {
                                "apiName": "AccountSource"
                            },
                            {
                                "apiName": "Website"
                            }
                        ]
                    ]
                },
                {
                    "name": "addressInformation",
                    "label": "Address Information",
                    "active": true,
                    "cols": 2,
                    "rows": [
                        [
                            {
                                "apiName": "BillingStreet"
                            },
                            {
                                "apiName": "ShippingStreet"
                            }
                        ]
                    ]
                }
            ]
        };
        component.set('v.defaultValues', JSON.stringify({}));
        component.set('v.recordLayoutJSON', JSON.stringify(recordLayoutJSON));
        component.set('v.isReady', true);
    },
    handleSuccess : function(component, event, helper) { 
        let item = event.getParam('value');
        console.log('item', item);
    }  
})
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### **Delegated Save**

If you prefer complete control over the save logic, enable `delegatedSave`.\
In this mode, the new record or changes are exposed in the `handleSubmit` method, and the **database save operation** must be explicitly handled by the developer.

{% tabs fullWidth="true" %}
{% tab title="auraRecordLayout.cmp (Delegated Save)" %}
{% code expandable="true" %}

```xml
<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">
                
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <aura:if isTrue="{!v.isReady}">
        <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                        sObjectName="{!v.sObjectName}"
                                        editMode="true"
                                        
                                        recordId="{!v.recordId}"
                                        recordIdField="recordId"
                                        targetObjName="Account" 
                                        
                                        recordLayout="{!v.recordLayoutJSON}"
                                        
                                        defaultValues="{!v.defaultValues}"
                                        
                                        delegatedSave="true"
                                        onsubmit="{!c.handleSubmit}"
                                        />
    </aura:if>
</aura:component>
```

{% endcode %}
{% endtab %}

{% tab title="auraRecordLayout.js (Delegated Save) " %}
{% code expandable="true" %}

```javascript
({
    doInit : function(component, event, helper) { 
        const recordLayoutJSON = {
            "density": "comfy",
            "sections": [
                {
                    "name": "accountInformation",
                    "label": "Account Information",
                    "active": true,
                    "cols": 3,
                    "rows": [
                        [
                            {
                                "apiName": "Name"
                            },
                            {
                                "apiName": "AccountSource"
                            },
                            {
                                "apiName": "Website"
                            }
                        ]
                    ]
                },
                {
                    "name": "addressInformation",
                    "label": "Address Information",
                    "active": true,
                    "cols": 2,
                    "rows": [
                        [
                            {
                                "apiName": "BillingStreet"
                            },
                            {
                                "apiName": "ShippingStreet"
                            }
                        ]
                    ]
                }
            ]
        };
        
        component.set('v.defaultValues', JSON.stringify({}));
        component.set('v.recordLayoutJSON', JSON.stringify(recordLayoutJSON));
        component.set('v.isReady', true);
    },
    handleSubmit : function(component, event, helper) { 
        let item = event.getParam('value');
        console.log('item', item);
        
        // how to set record/global errors
        let recordErrors = [
            {
                message: "Invalid Name"
            }
        ];
        
        // how to set fields errors
        let fieldErrors = [
            {
                fieldApiName : "Name",
                errors : [
                    {
                        message: "Invalid Name"
                    } 
                ]
            }
        ];
        
        let recordLayout = component.find('recordLayout');
        recordLayout.setErrors(recordErrors, fieldErrors);
    }
})
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
When both `recordId` and `recordIdField` are provided, the component can load and edit an existing record:\
&#x20;       \- `recordId` : identifies which record to load.\
&#x20;       \- `recordIdField` : specifies where the record ID is stored in the layout.

If NOT, there’s no record context. \
So the layout opens as a blank form with default or empty fields, ready for new record creation.
{% endhint %}

#### **No action bar**

It allows you to add a footer section with **Save** and **other custom buttons** that aren’t part of the default layout action bar.

The Save button calls `recordLayout.saveRecord()` to save the current record.

{% tabs fullWidth="true" %}
{% tab title="noActionBar.cmp" %}
{% code fullWidth="true" expandable="true" %}

```xml
<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">

    <!-- Attributes -->
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />

    <!-- Init Handler -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <!-- Main Body -->
    <aura:if isTrue="{!v.isReady}">
        <div class="slds-card slds-p-around_medium">

            <!-- Record Layout -->
            <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                            sObjectName="{!v.sObjectName}"
                                            editMode="true"
                                            noActionBar="true"
                                            targetObjName="Account"
                                            recordLayout="{!v.recordLayoutJSON}"
                                            defaultValues="{!v.defaultValues}"
                                            onsuccess="{!c.handleSuccess}"
                                            />

            <!-- Footer with Buttons -->
            <div class="slds-text-align_center slds-m-top_medium">
                <lightning:button label="Cancel"
                                  variant="neutral"
                                  onclick="{!c.handleCancel}" />

                <lightning:button label="Save"
                                  variant="brand"
                                  class="slds-m-left_small"
                                  onclick="{!c.handleSave}" />
            </div>

        </div>
    </aura:if>
</aura:component>
```

{% endcode %}
{% endtab %}

{% tab title="noActionBar.js" %}
{% code expandable="true" %}

```javascript
({
    doInit: function(component, event, helper) { 
        const recordLayoutJSON = {
            "density": "comfy",
            "sections": [
                {
                    "name": "accountInformation",
                    "label": "Account Information",
                    "active": true,
                    "cols": 3,
                    "rows": [
                        [
                            { "apiName": "Name" },
                            { "apiName": "AccountSource" },
                            { "apiName": "Website" }
                        ]
                    ]
                },
                {
                    "name": "addressInformation",
                    "label": "Address Information",
                    "active": true,
                    "cols": 2,
                    "rows": [
                        [
                            { "apiName": "BillingStreet" },
                            { "apiName": "ShippingStreet" }
                        ]
                    ]
                }
            ]
        };

        component.set("v.defaultValues", JSON.stringify({}));
        component.set("v.recordLayoutJSON", JSON.stringify(recordLayoutJSON));
        component.set("v.isReady", true);
    },

    handleSave: function(component, event, helper) {
        console.log('handleSave');

        const recordLayout = component.find("recordLayout");
        recordLayout.saveRecord();
    },

    handleCancel: function(component, event, helper) {
        // Cancel action
        console.log('handleCancel')
    }
});
```

{% endcode %}
{% endtab %}

{% tab title="noActionBar.css" %}

```css
// if you want to remove box-shadow and padding 
.THIS .risen {
    box-shadow: none;
    padding: 0;
}
```

{% endtab %}
{% endtabs %}

## Record Layout Description

#### 1. Overall Structure

The JSON defines a record layout configuration for a UI component.

At the top level:

* density → controls field spacing ("comfy", "compact").
* sections → an array of section objects, each defining its own layout and behavior.

#### 2. Section object properties

Each section in "sections": \[ … ] has these key properties:

<table data-full-width="true"><thead><tr><th width="114.01171875">Property</th><th>Description</th></tr></thead><tbody><tr><td><strong>name</strong></td><td><p>Internal unique identifier for the section (used in logic).</p><pre class="language-json"><code class="lang-json">{
  "name": "accountInformation"
}
</code></pre></td></tr><tr><td><strong>label</strong></td><td><p>The display title of the section, shown to users.</p><pre class="language-json"><code class="lang-json">{
  "label": "Account Information"
}
</code></pre></td></tr><tr><td><strong>active</strong></td><td><p>A flag (true/false or special value like "always") that controls if the section is enabled. If set to "always" it means the section is always active.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "name": "contactInformation",
  "label": "Contact Information",
  "active": "always",
  "cols": 2,
  "rows": [
    [
      { "apiName": "Email" },
      { "apiName": "Phone" }
    ]
  ]
}
</code></pre></td></tr><tr><td><strong>cols</strong></td><td><p>Number of columns to display in that section. This controls layout/grid: how many fields (or blocks) appear per row.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "name": "accountInformation",
  "label": "Account Information",
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Phone" },
      { "apiName": "Website" }
    ]
  ]
}
</code></pre></td></tr><tr><td><strong>visibility</strong></td><td><p>A visibility condition (JSON object) that determines whether the entire section is visible or hidden based on field values of the record. This follows the same rule‐structure as the “readOnly” conditions (operator, value). </p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "name": "addressInformation",
  "label": "Address Information",
  "active": true,
  "visibility": {
    "BillingCountry": { "operator": "=", "value": "USA" }
  },
  "cols": 2,
  "rows": [
    [
      { "apiName": "BillingStreet" },
      { "apiName": "ShippingStreet" }
    ]
  ]
}
</code></pre></td></tr><tr><td><strong>readOnly</strong></td><td><p>A read‐only condition for the whole section. If this evaluates true, all fields in the section become non‐editable.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "name": "accountInformation",
  "label": "Account Information",
  "active": true,
  "readOnly": {
    "Industry": { "operator": "=", "value": "Banking" }
  },
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Industry" },
      { "apiName": "Phone" }
    ]
  ]
}
</code></pre></td></tr><tr><td><strong>rows</strong></td><td><p>An array of rows. Each row is itself an array of “cells” (either field definitions or emptyBlock definitions) that fill up to cols columns. Fields can span columns using colSize.</p><pre class="language-json" data-full-width="true" data-expandable="true"><code class="lang-json">{
  "name": "accountInformation",
  "label": "Account Information",
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Phone" },
      { "apiName": "Website" }
    ],
    [
      { "apiName": "Description", "colSize": 2 },
      { "emptyBlock": true }
    ]
  ]
}
</code></pre></td></tr></tbody></table>

#### 3. Field (cell) object properties

Inside rows, each cell can be:

* A field object with properties, or
* An empty block object: { "emptyBlock": true } (used for layout alignment).

For field objects, common properties include:

<table data-full-width="true"><thead><tr><th width="134.5">Property</th><th>Description</th></tr></thead><tbody><tr><td><strong>apiName</strong></td><td><p>The API name of the field in the data model that will be displayed.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "Name"
}
</code></pre></td></tr><tr><td><strong>colSize</strong></td><td><p>Optional. Defines how many columns wide the field spans (useful when a field should cover more than a single column).</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "Description",
  "colSize": 2
}
</code></pre></td></tr><tr><td><strong>noWhitespace</strong></td><td><p>Optional boolean. If true, removes extra whitespace/margins around the field component.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "Industry",
  "noWhitespace": true
}
</code></pre></td></tr><tr><td><strong>readOnly</strong></td><td><p>A JSON condition (like visibility) that determines if the field is editable or not. Even if the section is editable, this field can individually be read‐only.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "AccountSource",
  "readOnly": {
    "AccountSource": { "operator": "in", "value": ["Web", "Other"] }
  }
}
</code></pre></td></tr><tr><td><strong>visibility</strong></td><td><p>A JSON condition that determines whether the field is shown at all.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "Industry",
  "visibility": {
    "BillingCountry": { "operator": "!=", "value": "USA" }
  }
}
</code></pre></td></tr><tr><td><strong>coloring</strong></td><td><p>An array of coloring rules: each rule has a color value and an exp object that defines a condition under which that color is applied to the field. This allows conditional highlighting.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "Status",
  "coloring": [
    {
      "color": "#a6f79e",
      "exp": { "Status": { "operator": "=", "value": "Active" } }
    }
  ]
}
</code></pre></td></tr><tr><td><strong>emptyBlock</strong></td><td><p>If the object is { "emptyBlock": true }, it means “insert an empty slot here” for layout purposes (no field displayed).</p><pre class="language-json" data-expandable="true"><code class="lang-json">[
  { "apiName": "Description", "colSize": 2 },
  { "emptyBlock": true }
]
</code></pre></td></tr><tr><td><strong>autocomplete</strong></td><td><p>Provides a City field with an autocomplete dropdown, suggesting values like <em>Paris</em>, <em>London</em>, <em>New York</em>, and <em>Rome</em> as the user types.</p><pre class="language-json" data-expandable="true"><code class="lang-json">{
  "apiName": "City",
  "autocomplete": ["Paris", "London", "New York", "Rome"]
}
</code></pre></td></tr></tbody></table>

## Examples

<details>

<summary>Advanced Layout (conditional visibility, read-Only, and autocomplete)</summary>

#### **Section 1 : Account Information**

* Active: always.
* Read-only when: Industry = Banking.
* 3-column grid layout.

**4 rows :**

1. Name, AccountSource, Industry&#x20;
   * AccountSource: read-only if “Web” or “Other”; turns green (#a6f79e) when “Web”.
   * Industry: visible only when BillingCountry != USA and with no extra whitespace.
2. Phone, Fax, and one conditional emptyBlock that appears only when BillingCountry = USA.
3. Website, Description (spanning 2 columns).
4. BillingCity field uses autocomplete with static options: “Paris”, “New York”, “London”, “Rome”.

***

#### **Section 2 : Address Information**

* Visible when: BillingCountry = USA.
* Read-only when: BillingCity = New York.
* 2-column layout.

**3 rows :**

1. BillingStreet, ShippingStreet.
2. BillingCity, ShippingCity.
3. BillingPostalCode, ShippingPostalCode.

***

#### **Section 3 : SSN Information**

* Visible when: Account.BillingCountry = USA.
* Read-only when: Account.Type != Customer.
* 1-column layout.
* Displays a single field SSN\_\_c.

{% code expandable="true" %}

```json
{
            "density": "comfy",
            "sections": [
                {
                    "name": "accountInformation",
                    "label": "Account Information",
                    "active": "always",
                    "cols": 3,
                    "readOnly": {
                        "Industry": { "operator": "=", "value": "Banking" }
                    },
                    "rows": [
                        [
                            { "apiName": "Name" },
                            {
                                "apiName": "AccountSource",
                                "readOnly": {
                                    "AccountSource": { "operator": "in", "value": ["Web", "Other"] }
                                },
                                "coloring": [
                                    {
                                        "color": "#a6f79e",
                                        "exp": { "AccountSource": { "operator": "=", "value": "Web" } }
                                    }
                                ]
                            },
                            {
                                "apiName": "Industry",
                                "noWhitespace": true,
                                "visibility": {
                                    "BillingCountry": { "operator": "!=", "value": "USA" }
                                }
                            }
                        ],
                        [
                            {
                                "emptyBlock": true,
                                "visibility": {
                                    "BillingCountry": { "operator": "=", "value": "USA" }
                                }
                            },
                            { "apiName": "Phone" },
                            { "apiName": "Fax" }
                        ],
                        [
                            { "apiName": "Website" },
                            { "apiName": "Description", "colSize": 2 }
                        ],
                        [
                            {
                                "apiName": "BillingCity",
                                "autocomplete": ["Paris", "New York", "London", "Rome"]
                            },
                            { "emptyBlock": true },
                            { "emptyBlock": true }
                        ]
                    ]
                },
                {
                    "name": "addressInformation",
                    "label": "Address Information",
                    "active": true,
                    "cols": 2,
                    "visibility": {
                        "BillingCountry": { "operator": "=", "value": "USA" }
                    },
                    "readOnly": {
                        "BillingCity": { "operator": "=", "value": "New York" }
                    },
                    "rows": [
                        [
                            { "apiName": "BillingStreet" },
                            { "apiName": "ShippingStreet" }
                        ],
                        [
                            { "apiName": "BillingCity" },
                            { "apiName": "ShippingCity" }
                        ],
                        [
                            { "apiName": "BillingPostalCode" },
                            { "apiName": "ShippingPostalCode" }
                        ]
                    ]
                }
            ]
        }
```

{% endcode %}

</details>

<details>

<summary>Minimal Contact Info (2 columns, autocomplete, emptyBlock)</summary>

```json
{
  "density": "comfy",
  "sections": [
    {
      "name": "contactInfo",
      "label": "Contact Information",
      "active": true,
      "cols": 2,
      "rows": [
        [
          { "apiName": "FirstName" },
          { "apiName": "LastName" }
        ],
        [
          { "apiName": "Email" },
          { "apiName": "Phone" }
        ],
        [
          { "apiName": "MailingCity", "autocomplete": ["Paris", "London", "New York", "Rome"] },
          { "emptyBlock": true }
        ]
      ]
    }
  ]
}
```

</details>

<details>

<summary>Opportunity Summary (3 columns, field visibility, coloring)</summary>

```json
{
  "density": "comfy",
  "sections": [
    {
      "name": "opptySummary",
      "label": "Opportunity Summary",
      "active": "always",
      "cols": 3,
      "rows": [
        [
          { "apiName": "Name" },
          {
            "apiName": "StageName",
            "coloring": [
              { "color": "#a6f79e", "exp": { "StageName": { "operator": "in", "value": ["Prospecting", "Qualification"] } } }
            ]
          },
          { "apiName": "CloseDate" }
        ],
        [
          { "apiName": "Amount" },
          {
            "apiName": "Competitor__c",
            "visibility": { "Type": { "operator": "=", "value": "New Business" } }
          },
          { "apiName": "OwnerId" }
        ],
        [
          { "apiName": "Description", "colSize": 2 },
          { "emptyBlock": true }
        ]
      ]
    }
  ]
}
```

</details>

<details>

<summary>Case Details (section readOnly + field visibility)</summary>

```json
{
  "density": "comfy",
  "sections": [
    {
      "name": "caseDetails",
      "label": "Case Details",
      "active": true,
      "cols": 2,
      "readOnly": {
        "Status": { "operator": "in", "value": ["Closed", "Resolved"] }
      },
      "rows": [
        [
          { "apiName": "Subject" },
          { "apiName": "Priority" }
        ],
        [
          {
            "apiName": "SLA__c",
            "coloring": [
              { "color": "#f7b267", "exp": { "SLA__c": { "operator": "=", "value": "At Risk" } } }
            ]
          },
          {
            "apiName": "Escalated__c",
            "visibility": { "Priority": { "operator": "=", "value": "High" } }
          }
        ],
        [
          { "apiName": "Description", "colSize": 2 }
        ]
      ]
    }
  ]
}
```

</details>

## CSS tips

<details>

<summary>Fix: Prevent Input Validation Error Bumps</summary>

{% code expandable="true" %}

```css
.THIS .slds-form-element__help {
    min-height: 20px;
}
```

{% endcode %}

</details>

<details>

<summary>Enforce Read-Only Mode (Hide Edit Icon)</summary>

```css
.THIS .inline-edit-trigger {
    display: none;
}
```

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.gridmate.io/product-tour/record-layout-lwc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
