Webpack plugin that automatically generates AEM component dialog XML, design dialogs, and policies from JSON configurations
npm install aem-dialog-generator-plugin_cq_dialog.xml and _cq_design_dialog.xml files with associated policies from simple JSON configurations. Features intelligent automation including automatic style tab generation, policy-to-template mapping, and dynamic indentation for production-ready AEM components.
_cq_dialog/.content.xml and _cq_dialog.xml formats
bash
npm install aem-dialog-generator-plugin --save-dev
`
Quick Start
$3
`javascript
const AemDialogGeneratorPlugin = require('aem-dialog-generator-plugin');
const path = require('path');
module.exports = {
plugins: [
new AemDialogGeneratorPlugin({
sourceDir: path.resolve(__dirname, 'src/main/webpack/components'),
targetDir: path.resolve(__dirname, '../ui.apps/src/main/content/jcr_root/apps/mysite/components'),
appName: 'mysite',
generatePolicies: true,
policiesTargetDir: path.resolve(__dirname, '../ui.content/src/main/content/jcr_root/conf/mysite/settings/wcm/policies'),
templatePoliciesDir: path.resolve(__dirname, '../ui.content/src/main/content/jcr_root/conf/mysite/settings/wcm/templates'),
autoMapPoliciesToTemplates: true
})
]
};
`
$3
Create src/main/webpack/components/button/dialog.json:
`json
{
"title": "Button Component",
"tabs": [
{
"title": "Properties",
"fields": [
{
"type": "textfield",
"name": "./text",
"label": "Button Text",
"required": true
},
{
"type": "pathfield",
"name": "./link",
"label": "Link",
"rootPath": "/content"
},
{
"type": "select",
"name": "./style",
"label": "Button Style",
"options": [
{ "value": "primary", "text": "Primary" },
{ "value": "secondary", "text": "Secondary" }
]
}
]
}
]
}
`
$3
`bash
npm run dev
`
The plugin automatically generates:
`
ui.apps/src/main/content/jcr_root/apps/mysite/components/button/_cq_dialog/.content.xml
`
Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| sourceDir | String | Required | Folder containing component dialog.json files |
| targetDir | String | Required | Target folder for generated XML files |
| dialogFileName | String | dialog.json | Name of the JSON configuration file |
| designDialogFileName | String | designDialog.json | Name of the design dialog JSON configuration file |
| appName | String | mysite | AEM application name |
| useFolderStructure | Boolean | true | Use _cq_dialog/.content.xml (true) or _cq_dialog.xml (false) |
| verbose | Boolean | false | Enable detailed logging |
| generatePolicies | Boolean | true | Enable automatic policy generation from designDialog.json |
| policiesTargetDir | String | ../ui.content/.../policies | Target folder for generated policy XML files |
| templatePoliciesDir | String | ../ui.content/.../templates | Target folder for template policy mapping |
| autoMapPoliciesToTemplates | Boolean | true | Automatically map policies to specified templates |
Supported Field Types
$3
#### textfield
`json
{
"type": "textfield",
"name": "./title",
"label": "Title",
"description": "Enter title",
"required": true,
"defaultValue": "Default text"
}
`
New Properties Quick Guide
$3
Force the JCR data type saved by Sling. Useful for numbers, booleans, or arrays.
Examples:
`json
{ "type": "textfield", "name": "./views", "label": "Views", "typeHint": "Long" }
{ "type": "select", "name": "./tags", "label": "Tags", "multiple": true, "typeHint": "String[]", "options": [{"value":"a","text":"A"}] }
`
$3
Add CSS classes to the field container; merged with other Granite classes.
`json
{ "type": "textfield", "name": "./title", "label": "Title", "className": "input-sm", "wrapperClass": "wrap-a wrap-b" }
`
$3
Focus the field when the dialog opens.
`json
{ "type": "textfield", "name": "./search", "label": "Search", "autoFocus": true }
`
$3
Complement trackingFeature to identify a specific element interaction.
`json
{ "type": "textfield", "name": "./ctaText", "label": "CTA Text", "trackingFeature": "hero", "trackingElement": "cta" }
`
What's New in v2.0.0 π
Version 2.0.0 introduces powerful automation features that significantly reduce manual configuration and improve developer productivity:
$3
- Auto Style Tab: Automatically generates cq:styles tab when policies contain styleGroups
- Template Mapping: One-click policy deployment to multiple templates
- Dynamic Indentation: Perfect XML formatting regardless of complexity
$3
- Hierarchical Policies: Proper AEM policy directory structure (/conf/{app}/settings/wcm/policies/{app}/components/)
- Consistent XML: Unified XML generation using buildNode() for reliability
- Smart Detection: Automatically detects when style system integration is needed
$3
- Zero Configuration: Default settings work out of the box
- Automatic Deployment: Policies are instantly mapped to templates
- Production Ready: Generated XML follows all AEM best practices
$3
- 239 Tests: Complete test coverage including all new automation features
- Regression Protection: Ensures backward compatibility with existing configurations
- Edge Case Handling: Robust validation and error handling
Design Dialogs & Component Policies
The plugin now supports generating Design Dialogs (_cq_design_dialog) and their associated component policies. This simplifies the complex task of configuring component policies in AEM.
$3
Create a designDialog.json file alongside your dialog.json:
Example: src/main/webpack/components/button/designDialog.json
`json
{
"title": "Button Design",
"layout": "simple",
"fields": [
{
"type": "checkbox",
"name": "./enableVariants",
"label": "Enable Button Variants",
"description": "Allow authors to choose between different button styles"
},
{
"type": "checkbox",
"name": "./enableSizes",
"label": "Enable Size Options"
}
],
"policy": {
"name": "policy_button",
"title": "Button Policy",
"description": "Policy configuration for button component",
"properties": {
"allowedVariants": "[primary,secondary,outline]",
"enableAnimation": "{Boolean}true"
},
"styleGroups": [
{
"name": "variants",
"label": "Button Variants",
"styles": [
{
"name": "primary",
"label": "Primary",
"classes": "cmp-button--primary"
},
{
"name": "secondary",
"label": "Secondary",
"classes": "cmp-button--secondary"
}
]
}
]
}
}
`
$3
The policy object in your designDialog.json supports:
#### Basic Policy Properties
`json
{
"policy": {
"name": "policy_mycomponent",
"title": "My Component Policy",
"description": "Policy description shown in template editor",
"properties": {
"customProperty": "value",
"enableFeature": "{Boolean}true",
"maxItems": "{Long}5"
}
}
}
`
#### Style System Configuration
Define style groups for the AEM Style System:
`json
{
"policy": {
"styleGroups": [
{
"name": "layout",
"label": "Layout Options",
"styles": [
{
"name": "grid",
"label": "Grid Layout",
"classes": "cmp-container--grid",
"icon": "viewGrid"
},
{
"name": "flex",
"label": "Flex Layout",
"classes": "cmp-container--flex"
}
]
}
]
}
}
`
#### RTE Plugin Configuration
Configure Rich Text Editor plugins in policies:
`json
{
"policy": {
"rtePlugins": {
"format": {
"features": "bold,italic,underline"
},
"paraformat": {
"features": "*",
"formats": [
{ "description": "Heading 1", "tag": "h1" },
{ "description": "Heading 2", "tag": "h2" },
{ "description": "Paragraph", "tag": "p" }
]
},
"links": {
"features": "modifylink,unlink"
},
"lists": {
"features": "*"
},
"justify": {
"features": "-"
},
"table": {
"features": "-"
}
}
}
}
`
#### Component Mapping
Configure asset-to-component drag & drop mappings:
`json
{
"policy": {
"componentMapping": [
{
"assetGroup": "media",
"assetMimetype": "image/*",
"droptarget": "image",
"resourceType": "mysite/components/image"
},
{
"assetGroup": "content",
"assetMimetype": "text/html",
"droptarget": "experiencefragment",
"resourceType": "mysite/components/experiencefragment"
}
]
}
}
`
$3
Hero Component with Full Policy Configuration:
`json
{
"title": "Hero Design",
"layout": "tabs",
"tabs": [
{
"title": "Layout Options",
"fields": [
{
"type": "select",
"name": "./defaultLayout",
"label": "Default Layout",
"defaultValue": "centered",
"options": [
{ "text": "Centered", "value": "centered" },
{ "text": "Left Aligned", "value": "left" },
{ "text": "Right Aligned", "value": "right" }
]
},
{
"type": "checkbox",
"name": "./allowBackgroundImage",
"label": "Allow Background Image"
}
]
}
],
"policy": {
"name": "policy_hero",
"title": "Hero Component Policy",
"description": "Comprehensive policy for hero component",
"properties": {
"allowedLayouts": "[centered,left,right]",
"minHeight": "{Long}400",
"maxHeight": "{Long}800"
},
"rtePlugins": {
"format": {
"features": "bold,italic"
},
"paraformat": {
"features": "*",
"formats": [
{ "description": "Heading 1", "tag": "h1" },
{ "description": "Paragraph", "tag": "p" }
]
},
"links": {
"features": "modifylink,unlink"
}
},
"styleGroups": [
{
"name": "layouts",
"label": "Hero Layouts",
"styles": [
{
"name": "centered",
"label": "Centered",
"classes": "cmp-hero--centered"
},
{
"name": "left",
"label": "Left Aligned",
"classes": "cmp-hero--left"
}
]
},
{
"name": "themes",
"label": "Color Themes",
"styles": [
{
"name": "light",
"label": "Light Theme",
"classes": "cmp-hero--light"
},
{
"name": "dark",
"label": "Dark Theme",
"classes": "cmp-hero--dark"
}
]
}
]
}
}
`
$3
When you build, the plugin generates:
1. Design Dialog XML: ui.apps/.../button/_cq_design_dialog/.content.xml
2. Policy XML: ui.content/.../policies/button/.content.xml
3. Template Mapping: Automatically maps policies to specified templates (when autoMapPoliciesToTemplates: true)
The policy XML is structured according to AEM standards and can be referenced in your page templates.
$3
The plugin now includes several automatic features to streamline AEM component development:
#### π¨ Automatic cq:styles Tab Generation
When a policy includes styleGroups, the plugin automatically generates a cq:styles tab in the design dialog:
`json
{
"policy": {
"styleGroups": [
{
"name": "variants",
"label": "Button Variants",
"styles": [...]
}
]
}
}
`
This automatically adds the AEM Style System tab to your design dialog without manual configuration.
#### π Automatic Policy-to-Template Mapping
Configure which templates should use a component policy by adding a templates array to your policy:
`json
{
"policy": {
"name": "policy_section_container",
"title": "Section Container Policy",
"description": "Policy for section component",
"templates": ["page-content", "landing-page"]
}
}
`
The plugin will automatically update the template policy files to include the mapping:
`xml
cq:policy="mysite/components/section/policy_section_container"
jcr:primaryType="nt:unstructured"
sling:resourceType="wcm/core/components/policies/mapping"/>
`
#### π Dynamic Indentation System
All generated XML uses a consistent, dynamic indentation system that ensures properly formatted output regardless of nesting level or complexity.
#### π Correct Policy Structure
Policies are automatically generated in the proper AEM directory structure:
`
/conf/{appName}/settings/wcm/policies/{appName}/components/{componentName}/.content.xml
`
This follows AEM best practices and ensures policies are properly organized.
#### textarea
`json
{
"type": "textarea",
"name": "./description",
"label": "Description",
"rows": 5
}
`
#### pathfield
`json
{
"type": "pathfield",
"name": "./link",
"label": "Link",
"rootPath": "/content"
}
`
#### select
`json
{
"type": "select",
"name": "./type",
"label": "Type",
"options": [
{ "value": "type1", "text": "Type 1" },
{ "value": "type2", "text": "Type 2" }
]
}
`
Advanced Properties
- Show/Hide by expression: use showIf or hideIf to emit granite:hide and control field visibility.
- Select datasource: add datasource (child node), emptyOption, forceSelection.
- Validation messages: override built-ins with requiredMessage, minMessage, maxMessage, patternMessage.
- Order fields: place with orderBefore (emits sling:orderBefore).
- Granite data: set data: { key: value } β granite:data-key="value" on the field.
- Render conditions: renderCondition supports simple, privilege, and, or with nested conditions.
- Multifield UX: addItemLabel, maxItemsMessage, minItemsMessage, reorderableHandle.
- QoL inputs: clearButton (textfield), autocomplete, ariaLabel, ariaDescribedBy, tooltipIcon.
Examples:
`json
{
"type": "textfield",
"name": "./videoUrl",
"label": "Video URL",
"showIf": { "field": "./contentType", "value": "video" },
"required": true,
"requiredMessage": "Required for Video",
"ariaLabel": "Video URL"
}
`
`json
{
"type": "select",
"name": "./category",
"label": "Category",
"emptyOption": true,
"forceSelection": true,
"datasource": "/apps/mysite/datasources/categories"
}
`
`json
{
"type": "textfield",
"name": "./adminOnly",
"label": "Admin Only",
"renderCondition": {
"type": "and",
"conditions": [
{ "type": "simple", "expression": "${currentUser == 'admin'}" },
{ "type": "privilege", "privilege": "jcr:read" }
]
}
}
`
`json
{
"type": "multifield",
"name": "./items",
"label": "Items",
"addItemLabel": "Add Item",
"maxItemsMessage": "Too many",
"minItemsMessage": "Too few",
"reorderableHandle": "drag",
"fields": [{ "type": "textfield", "name": "./item", "label": "Item" }]
}
`
#### checkbox
`json
{
"type": "checkbox",
"name": "./enabled",
"label": "Enabled",
"text": "Enable feature",
"value": "true"
}
`
#### numberfield
`json
{
"type": "numberfield",
"name": "./count",
"label": "Count",
"min": 0,
"max": 100,
"step": 1
}
`
#### datepicker
`json
{
"type": "datepicker",
"name": "./date",
"label": "Date"
}
`
#### colorfield
`json
{
"type": "colorfield",
"name": "./color",
"label": "Color"
}
`
#### switch
`json
{
"type": "switch",
"name": "./active",
"label": "Active",
"checked": true
}
`
#### hidden
`json
{
"type": "hidden",
"name": "./hiddenValue",
"value": "hidden-value"
}
`
#### fileupload
`json
{
"type": "fileupload",
"name": "./fileReference",
"label": "File"
}
`
#### hidden
`json
{
"type": "hidden",
"name": "./componentId",
"defaultValue": "auto-generated-id"
}
`
Stores values in JCR without displaying them in the dialog. Perfect for:
- Auto-generated IDs or timestamps
- Technical flags or metadata
- Calculated values set by JavaScript
- Values that authors shouldn't modify
Note: Hidden fields have no label or visual representation.
#### button
`json
{
"type": "button",
"text": "Generate Content",
"variant": "primary",
"icon": "magic",
"command": "generateContent"
}
`
Adds clickable buttons to trigger actions:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| text | String | Button label | "Button" |
| variant | String | Style: primary, secondary, action, warning | primary |
| icon | String | Coral UI icon name | - |
| command | String | Command to execute | - |
| handler | String | JavaScript handler file | - |
Use Cases:
- Trigger content generation
- Clear form fields
- Preview content
- Execute custom actions
- Integration with client libraries
$3
#### fieldset - Group Related Fields
`json
{
"type": "fieldset",
"label": "SEO Settings",
"fields": [
{
"type": "textfield",
"name": "./metaTitle",
"label": "Meta Title"
},
{
"type": "textarea",
"name": "./metaDescription",
"label": "Meta Description"
}
]
}
`
#### container - Generic Container for Grouping
`json
{
"type": "container",
"fields": [
{
"type": "textfield",
"name": "./option1",
"label": "Option 1"
},
{
"type": "textfield",
"name": "./option2",
"label": "Option 2"
}
]
}
`
Differences:
- fieldset: Form-specific grouping with a visible label (jcr:title). Requires label property.
- container: Generic grouping element without visual label. The name property is optional (used only for node naming).
Note: Both support showhideClass for hiding entire groups of fields together.
#### fixedcolumns - Multi-Column Layout
`json
{
"type": "fixedcolumns",
"columns": [
{
"fields": [
{ "type": "textfield", "name": "./firstName", "label": "First Name" },
{ "type": "textfield", "name": "./email", "label": "Email" }
]
},
{
"fields": [
{ "type": "textfield", "name": "./lastName", "label": "Last Name" },
{ "type": "textfield", "name": "./phone", "label": "Phone" }
]
}
]
}
`
Organizes fields into side-by-side columns for better space utilization. Perfect for forms with related fields.
Properties:
| Property | Type | Description |
|----------|------|-------------|
| columns | Array | Array of column objects, each containing a fields array |
| name | String | Optional custom node name |
Column Properties:
- name (String): Optional custom column name (default: column1, column2, etc.)
- fields (Array): Array of field definitions for this column
Common Use Cases:
- Name and contact info side by side
- Address fields in multiple columns
- Date ranges (From/To)
- Compact form layouts
#### well - Visual Grouping Container
`json
{
"type": "well",
"name": "advancedSettings",
"fields": [
{ "type": "textfield", "name": "./cssClass", "label": "CSS Class" },
{ "type": "numberfield", "name": "./zIndex", "label": "Z-Index" },
{ "type": "checkbox", "name": "./customBehavior", "label": "Enable Custom Behavior" }
]
}
`
A well is a container with a subtle gray background that visually groups related fields.
Properties:
| Property | Type | Description |
|----------|------|-------------|
| fields | Array | Array of field definitions to display in the well |
| name | String | Optional custom node name |
When to Use:
- Highlighting optional or advanced settings
- Visually separating field groups without tabs
- Drawing attention to important configuration sections
- Creating visual hierarchy within a tab
Visual Effect: Fields appear with a light gray background, creating a subtle "inset" appearance that groups them together.
#### heading - Section Heading
`json
{
"type": "heading",
"text": "Advanced Settings",
"level": 3
}
`
Creates a visual heading element to organize dialog sections. Does not store any data.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| text | String | The heading text to display (required) | - |
| level | Number | Heading level (1-6) | 3 |
#### text - Informational Text / Alert
`json
{
"type": "text",
"text": "This setting will affect all child pages.",
"variant": "warning"
}
`
Displays static informational text or alerts. Does not store any data. Also accepts "type": "alert" as an alias.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| text | String | The message to display (required) | - |
| variant | String | Visual style: info, warning, error, success | info |
Common Use Cases:
- Help text and instructions
- Warnings about field impacts
- Error messages or important notes
- Success confirmation messages
#### tags - AEM Tag Selector
`json
{
"type": "tags",
"name": "./cq:tags",
"label": "Tags",
"required": true,
"rootPath": "/content/cq:tags/mysite"
}
`
Provides an AEM tag picker that allows users to select from the AEM tagging system.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name where selected tags are stored (required) | - |
| label | String | Field label (required) | - |
| rootPath | String | Root path in tag hierarchy | /content/cq:tags |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Content categorization
- SEO keywords
- Content filtering and search
- Taxonomy management
#### image - Image Upload and Selection
`json
{
"type": "image",
"name": "./image",
"label": "Image",
"required": true,
"uploadUrl": "/content/dam/mysite",
"allowUpload": true
}
`
Provides an image upload field with DAM integration. Supports drag-and-drop, file browsing, and DAM asset selection.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| uploadUrl | String | Upload destination path in DAM | - |
| allowUpload | Boolean | Enable file upload | true |
| mimeTypes | Array | Allowed mime types | ['image/gif', 'image/jpeg', 'image/png', 'image/webp', 'image/tiff', 'image/svg+xml'] |
| fileNameParameter | String | Property for filename | ./fileName |
| fileReferenceParameter | String | Property for file reference | ./fileReference |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Hero images and banners
- Product images
- Author avatars
- Background images
#### autocomplete - Autocomplete Field
`json
{
"type": "autocomplete",
"name": "./product",
"label": "Select Product",
"multiple": true,
"datasource": "/apps/mysite/datasources/products"
}
`
Provides an autocomplete field with optional datasource integration for dynamic suggestions.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| datasource | String | Path to datasource for suggestions | - |
| multiple | Boolean | Allow multiple selections | false |
| forceSelection | Boolean | Only allow values from suggestions | true |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Product selection
- User search
- Category selection
- Dynamic value lists
#### radiogroup - Radio Button Group
`json
{
"type": "radiogroup",
"name": "./layout",
"label": "Layout",
"vertical": false,
"options": [
{ "value": "grid", "text": "Grid" },
{ "value": "list", "text": "List", "checked": true }
]
}
`
Displays a group of radio buttons. Better than select when you have 2-4 options that should be immediately visible.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| options | Array | Radio button options (required) | - |
| vertical | Boolean | Stack radio buttons vertically | false |
| required | Boolean | Make field mandatory | false |
Option Properties:
- value (String): The value to store
- text (String): Display text
- checked (Boolean): Default selected option
Common Use Cases:
- Layout selection (2-3 options)
- Yes/No questions
- Content type selection
- Alignment options (left/center/right)
#### pagefield - AEM Page Selector
`json
{
"type": "pagefield",
"name": "./targetPage",
"label": "Link to Page",
"required": true,
"rootPath": "/content/mysite/en"
}
`
Provides an AEM-specific page selector with content tree navigation. Essential for any component that links to other pages.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| rootPath | String | Root path in content tree | /content |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Navigation links
- Call-to-action buttons
- Related content links
- Breadcrumb configuration
- Footer links
#### contentfragmentpicker - Content Fragment Selector
`json
{
"type": "contentfragmentpicker",
"name": "./fragmentPath",
"label": "Select Content Fragment",
"required": true,
"rootPath": "/content/dam/fragments",
"fragmentModel": "/conf/mysite/settings/dam/cfm/models/article"
}
`
Provides a Content Fragment picker for selecting structured content. Essential for AEM headless and content-driven sites.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| rootPath | String | Root path in DAM | /content/dam |
| fragmentModel | String | Path to specific Content Fragment Model | - |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Article content references
- Product data integration
- Headless content delivery
- Structured data references
- Multi-channel content
#### experiencefragmentpicker - Experience Fragment Selector
`json
{
"type": "experiencefragmentpicker",
"name": "./xfPath",
"label": "Select Experience Fragment",
"required": true,
"rootPath": "/content/experience-fragments/mysite"
}
`
Provides an Experience Fragment picker for reusable component compositions. Perfect for headers, footers, and repeated content blocks.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| rootPath | String | Root path for XF | /content/experience-fragments |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Global headers and footers
- Reusable content blocks
- Multi-variant content
- Promotional banners
- Email templates
#### assetpicker - Generic Asset Selector
`json
{
"type": "assetpicker",
"name": "./assetPath",
"label": "Select Asset",
"required": true,
"rootPath": "/content/dam/videos",
"mimeTypes": ["video/mp4", "video/webm", "application/pdf"]
}
`
Provides a generic DAM asset picker with mime type filtering. More flexible than image for videos, documents, and other file types.
Properties:
| Property | Type | Description | Default |
|----------|------|-------------|---------|
| name | String | Property name (required) | - |
| label | String | Field label (required) | - |
| rootPath | String | Root path in DAM | /content/dam |
| mimeTypes | Array | Allowed mime types | - |
| required | Boolean | Make field mandatory | false |
Common Use Cases:
- Video assets
- PDF documents
- Downloadable files
- Audio files
- Mixed media selection
#### rte - Rich Text Editor
`json
{
"type": "rte",
"name": "./text",
"label": "Content",
"required": true,
"features": ["bold", "italic", "underline", "links", "lists"]
}
`
Use "features": ["*"] for all features, or specify individual ones:
- "bold", "italic", "underline" - Text formatting
- "links" - Hyperlinks
- "lists" - Ordered and unordered lists
- "justify" - Text alignment
Dynamic Show/Hide
The plugin supports AEM's built-in cq-dialog-dropdown-showhide and cq-dialog-checkbox-showhide scripts for conditional field visibility.
$3
Show different fields based on dropdown selection:
`json
{
"type": "select",
"name": "./contentType",
"label": "Content Type",
"cqShowHide": true,
"showhideTarget": ".content-fields",
"options": [
{
"text": "Image",
"value": "image"
},
{
"text": "Video",
"value": "video"
},
{
"text": "Text",
"value": "text"
}
]
}
`
Then define containers that will be shown/hidden based on the selected value:
`json
{
"type": "container",
"showhideClass": "content-fields",
"showhidetargetvalue": "image",
"fields": [
{
"type": "pathfield",
"name": "./imagePath",
"label": "Image Path",
"rootPath": "/content/dam"
},
{
"type": "textfield",
"name": "./altText",
"label": "Alt Text"
}
]
},
{
"type": "container",
"showhideClass": "content-fields",
"showhidetargetvalue": "video",
"fields": [
{
"type": "pathfield",
"name": "./videoUrl",
"label": "Video URL"
}
]
},
{
"type": "container",
"showhideClass": "content-fields",
"showhidetargetvalue": "text",
"fields": [
{
"type": "textarea",
"name": "./textContent",
"label": "Text Content"
}
]
}
`
$3
Show fields when checkbox is checked:
`json
{
"type": "checkbox",
"name": "./enableCustomSettings",
"label": "Enable Custom Settings",
"cqShowHide": true,
"showhideTarget": ".custom-settings"
},
{
"type": "textfield",
"name": "./customValue",
"label": "Custom Value",
"showhideClass": "custom-settings"
},
{
"type": "numberfield",
"name": "./customNumber",
"label": "Custom Number",
"showhideClass": "custom-settings"
}
`
$3
| Property | Type | Used On | Description |
|----------|------|---------|-------------|
| cqShowHide | Boolean | select, checkbox | Enable show/hide functionality |
| showhideTarget | String | select, checkbox | CSS selector of elements to show/hide (e.g., ".my-fields") |
| showhideClass | String | fieldset, container | CSS class for elements that will be shown/hidden (e.g., "my-fields") |
| showhidetargetvalue | String | fieldset, container | Value that triggers showing this container (used with showhideClass) |
Note: You can use showhideClass on fieldset or container types to hide entire groups of fields together.
Generated XML for dropdown:
`xml
granite:class="cq-dialog-dropdown-showhide"
...>
jcr:primaryType="nt:unstructured"
cq-dialog-dropdown-showhide-target=".content-fields"/>
sling:resourceType="granite/ui/components/coral/foundation/container"
granite:class="hide content-fields">
jcr:primaryType="nt:unstructured"
showhidetargetvalue="image"/>
`
Generated XML for checkbox:
`xml
granite:class="cq-dialog-checkbox-showhide"
...>
jcr:primaryType="nt:unstructured"
cq-dialog-checkbox-showhide-target=".custom-settings"/>
granite:class="hide custom-settings"
.../>
`
$3
`json
{
"title": "Media Component",
"tabs": [
{
"title": "Content",
"fields": [
{
"type": "select",
"name": "./mediaType",
"label": "Media Type",
"cqShowHide": true,
"showhideTarget": ".media-fields",
"defaultValue": "image",
"options": [
{
"text": "Image",
"value": "image"
},
{
"text": "Video",
"value": "video"
}
]
},
{
"type": "container",
"showhideClass": "media-fields",
"showhidetargetvalue": "image",
"fields": [
{
"type": "pathfield",
"name": "./imageAsset",
"label": "Image Asset",
"rootPath": "/content/dam"
},
{
"type": "textfield",
"name": "./imageAlt",
"label": "Alt Text"
}
]
},
{
"type": "container",
"showhideClass": "media-fields",
"showhidetargetvalue": "video",
"fields": [
{
"type": "pathfield",
"name": "./videoAsset",
"label": "Video Asset",
"rootPath": "/content/dam"
},
{
"type": "checkbox",
"name": "./videoAutoplay",
"label": "Autoplay Video"
}
]
},
{
"type": "checkbox",
"name": "./addCaption",
"label": "Add Caption",
"cqShowHide": true,
"showhideTarget": ".caption-fields"
},
{
"type": "container",
"showhideClass": "caption-fields",
"fields": [
{
"type": "textarea",
"name": "./caption",
"label": "Caption Text"
}
]
}
]
}
]
}
`
$3
You can use container or fieldset with showhideClass and showhidetargetvalue to hide multiple fields as a group:
`json
{
"type": "select",
"name": "./mode",
"label": "Display Mode",
"cqShowHide": true,
"showhideTarget": ".mode-settings",
"options": [
{ "text": "Simple", "value": "simple" },
{ "text": "Advanced", "value": "advanced" }
]
},
{
"type": "container",
"name": "advancedSettings",
"showhideClass": "mode-settings",
"showhidetargetvalue": "advanced",
"fields": [
{
"type": "textfield",
"name": "./customClass",
"label": "Custom CSS Class"
},
{
"type": "numberfield",
"name": "./customWidth",
"label": "Custom Width"
},
{
"type": "textfield",
"name": "./dataAttributes",
"label": "Data Attributes"
}
]
}
`
This will show/hide all three fields inside the container only when "Advanced" is selected. Note that container doesn't require a label - it's just a grouping element.
#### multifield - Repeatable Fields
Simple Multifield (single field repeated):
`json
{
"type": "multifield",
"name": "./tags",
"label": "Tags",
"minItems": 1,
"maxItems": 5,
"fields": [
{
"type": "textfield",
"name": "./tag",
"label": "Tag"
}
]
}
`
Composite Multifield (grouped fields repeated together):
`json
{
"type": "multifield",
"name": "./slides",
"label": "Slides",
"composite": true,
"minItems": 2,
"maxItems": 10,
"fields": [
{
"type": "textfield",
"name": "./title",
"label": "Title",
"required": true
},
{
"type": "textarea",
"name": "./description",
"label": "Description"
},
{
"type": "pathfield",
"name": "./image",
"label": "Image",
"rootPath": "/content/dam"
}
]
}
`
Multifield Properties:
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| minItems | Number | Minimum number of items required (0 or greater) | minItems: 1 |
| maxItems | Number | Maximum number of items allowed (1 or greater) | maxItems: 10 |
| composite | Boolean | Group multiple fields together in each item | composite: true |
Conditional Tabs
Tabs can be shown or hidden based on the value of another field:
`json
{
"title": "My Component",
"tabs": [
{
"title": "General",
"fields": [
{
"type": "checkbox",
"name": "./enableAdvanced",
"label": "Enable Advanced Features"
}
]
},
{
"title": "Advanced Settings",
"showIf": {
"field": "./enableAdvanced",
"value": true
},
"fields": [
{
"type": "textfield",
"name": "./customClass",
"label": "Custom CSS Class"
}
]
}
]
}
`
The "Advanced Settings" tab will only appear when the "Enable Advanced Features" checkbox is checked.
showIf Properties:
- field (String): Path to the field to check (e.g., ./enableAdvanced)
- value (Any): Value to compare against (true, false, "video", etc.)
Use Cases:
- Advanced/expert mode settings
- Content-type specific tabs (show video tab only when content type is "video")
- Optional feature configurations
- Progressive disclosure to simplify UX
Accordion Layout
Use layout: 'accordion' for collapsible sections instead of tabs:
`json
{
"title": "My Component",
"layout": "accordion",
"tabs": [
{
"title": "Basic Settings",
"active": true,
"fields": [
{ "type": "textfield", "name": "./title", "label": "Title" },
{ "type": "textarea", "name": "./description", "label": "Description" }
]
},
{
"title": "Advanced Options",
"fields": [
{ "type": "textfield", "name": "./cssClass", "label": "CSS Class" },
{ "type": "numberfield", "name": "./order", "label": "Display Order" }
]
}
]
}
`
Accordion Properties:
| Property | Type | Description |
|----------|------|-------------|
| layout | String | Set to "accordion" to use collapsible sections |
| tabs | Array | Array of section objects (reuses tabs structure) |
Section Properties:
- title (String): Section heading
- active (Boolean): If true, this section is expanded by default (default: false)
- fields (Array): Fields within this section
- name (String): Optional custom node name
When to use Accordion vs Tabs:
- Accordion: Many sections, progressive disclosure, or when authors need to see multiple sections at once
- Tabs: Few sections (2-5), mutually exclusive content, or when a cleaner single-view interface is preferred
Field Descriptions
Add helpful guidance text below any field using the description property:
`json
{
"type": "textfield",
"name": "./title",
"label": "Title",
"description": "Enter a short, descriptive title for this component",
"required": true
}
`
The description appears below the field in a lighter font, providing context and instructions to content authors.
$3
Add example text inside empty fields using the placeholder property:
`json
{
"type": "textfield",
"name": "./username",
"label": "Username",
"placeholder": "Enter your username"
}
`
Placeholder text appears in a lighter color inside empty fields and disappears when the user starts typing. Very useful for:
- Showing format examples ("MM/DD/YYYY")
- Providing input hints ("Type to search...")
- Clarifying expected values ("e.g., john.doe@example.com")
Supported on: textfield, textarea, numberfield, pathfield, select, autocomplete
$3
Set numeric range constraints on number fields:
`json
{
"type": "numberfield",
"name": "./age",
"label": "Age",
"min": 18,
"max": 99,
"placeholder": "Enter age between 18-99"
}
`
The min and max properties enforce numeric boundaries:
- min (Number): Minimum allowed value (inclusive)
- max (Number): Maximum allowed value (inclusive)
- Works with numberfield type
- Browser-native validation
Common Use Cases:
`json
// Percentage (0-100)
{ "type": "numberfield", "name": "./opacity", "label": "Opacity %", "min": 0, "max": 100 }
// Positive numbers only
{ "type": "numberfield", "name": "./quantity", "label": "Quantity", "min": 1 }
// Rating system
{ "type": "numberfield", "name": "./rating", "label": "Rating", "min": 1, "max": 5 }
`
$3
Control field interactivity:
`json
// Disabled field - grayed out, not submitted
{
"type": "textfield",
"name": "./calculatedValue",
"label": "Calculated Value",
"disabled": true,
"defaultValue": "Auto-generated"
}
// ReadOnly field - visible but not editable, submitted with form
{
"type": "textfield",
"name": "./timestamp",
"label": "Created Date",
"readOnly": true,
"defaultValue": "2024-01-15"
}
`
Properties:
- disabled (Boolean): Disables the field completely (default: false)
- readOnly (Boolean): Makes field read-only but still submits value (default: false)
Key Differences:
- Disabled: Field is grayed out and value is NOT submitted
- ReadOnly: Field looks normal but can't be edited, value IS submitted
Use Cases:
- Disabled: Conditional fields, insufficient permissions, calculated values
- ReadOnly: System-generated IDs, timestamps, inherited values, display-only info
$3
Allow selecting multiple options in dropdowns:
`json
{
"type": "select",
"name": "./categories",
"label": "Categories",
"multiple": true,
"options": [
{ "text": "News", "value": "news" },
{ "text": "Events", "value": "events" },
{ "text": "Blog", "value": "blog" }
]
}
`
The multiple property enables multi-select:
- Works on select and autocomplete field types
- Users can select multiple values using Ctrl/Cmd + click
- Values are stored as an array
Common Use Cases:
- Tag selection
- Category assignment
- Permission selection
- Feature toggles
$3
Add help icon with tooltip next to field labels:
`json
// Simple text tooltip
{
"type": "textfield",
"name": "./pattern",
"label": "RegEx Pattern",
"contextualHelp": "Enter a valid JavaScript regular expression"
}
// With external documentation link
{
"type": "select",
"name": "./layout",
"label": "Layout Type",
"contextualHelp": {
"text": "Choose the layout format for this component",
"url": "https://docs.example.com/layouts"
},
"options": [...]
}
`
The contextualHelp property adds a small β icon next to the field label:
- String value: Shows tooltip on hover
- Object value: Shows tooltip and optional "Learn more" link
- text (String): Tooltip content
- url (String): External documentation URL
Benefits:
- Keeps UI clean while providing detailed help
- Reduces need for lengthy field descriptions
- Links to external documentation for complex features
- Standard AEM pattern for contextual assistance
$3
Add custom CSS classes to any field using the className property:
`json
// Single class
{
"type": "textfield",
"name": "./title",
"label": "Title",
"className": "custom-field-style"
}
// Multiple classes (string with spaces)
{
"type": "select",
"name": "./type",
"label": "Type",
"className": "highlight-field required-indicator",
"options": [...]
}
// Multiple classes (array format)
{
"type": "textarea",
"name": "./description",
"label": "Description",
"className": ["large-textarea", "rich-editor"]
}
`
The className property allows you to:
- Apply custom styling to specific fields
- Add JavaScript selector hooks for custom behavior
- Highlight important or required fields visually
- Maintain consistent styling across similar fields
Note: Custom classes are merged with existing Granite UI classes (like show/hide classes) and added to the field's granite:class attribute.
$3
Control the width of individual fields using the width property:
`json
// Fixed pixel width
{
"type": "textfield",
"name": "./code",
"label": "Product Code",
"width": "150px"
}
// Percentage width
{
"type": "numberfield",
"name": "./quantity",
"label": "Qty",
"width": "30%"
}
// Numeric value (treated as pixels)
{
"type": "select",
"name": "./size",
"label": "Size",
"width": "200",
"options": [...]
}
`
The width property accepts:
- Pixel values: "100px", "250px"
- Percentages: "50%", "75%"
- Numeric strings: "200" (treated as pixels)
- CSS values: "auto", "fit-content"
Common Use Cases:
- Short input fields for codes, IDs, or numbers
- Compact layouts with multiple fields per row
- Consistent sizing across related fields
- Optimizing dialog space utilization
$3
Control vertical spacing between fields using the margin property:
`json
// Add margin above field
{
"type": "heading",
"text": "Advanced Settings",
"level": 3,
"margin": true
}
// No margin (tight layout)
{
"type": "textfield",
"name": "./field1",
"label": "Field 1",
"margin": false
}
// Default behavior (don't specify)
{
"type": "textfield",
"name": "./field2",
"label": "Field 2"
}
`
The margin property controls Coral UI's vertical spacing:
- true: Adds standard margin above the field
- false: Removes margin for tight layouts
- undefined: Uses Coral UI default spacing
When to Use:
- margin: true: Add visual separation before headings or new sections
- margin: false: Create compact, dense layouts; group tightly related fields
- Default: Most fields should use default Coral spacing
Common Patterns:
`json
// Section with visual breathing room
{
"type": "heading",
"text": "Section Title",
"margin": true
},
// Compact field group (address fields, name parts, etc.)
{
"type": "textfield",
"name": "./street1",
"label": "Street Address",
"margin": false
},
{
"type": "textfield",
"name": "./street2",
"label": "Apt/Suite",
"margin": false
}
`
$3
Set initial values for fields using the defaultValue property:
`json
// Text field with default
{
"type": "textfield",
"name": "./title",
"label": "Title",
"defaultValue": "Welcome Message"
}
// Number field with default
{
"type": "numberfield",
"name": "./quantity",
"label": "Quantity",
"defaultValue": 1,
"min": 1
}
// Checkbox with default checked state
{
"type": "checkbox",
"name": "./enabled",
"label": "Enable Feature",
"defaultValue": true
}
// Select with pre-selected option
{
"type": "select",
"name": "./theme",
"label": "Theme",
"defaultValue": "dark",
"options": [
{ "value": "light", "text": "Light" },
{ "value": "dark", "text": "Dark" }
]
}
`
The defaultValue property:
- Sets the initial value when component is first added
- Works with all field types (textfield, numberfield, checkbox, select, etc.)
- Values are stored in the value attribute in the XML
- Useful for sensible defaults that authors can modify
Common Use Cases:
- Default "Read More" text for CTAs
- Pre-set quantity to 1 in product components
- Enable features by default (opt-out vs opt-in)
- Default theme or style selection
$3
Add helpful descriptive text below field labels using the description property:
`json
{
"type": "textfield",
"name": "./email",
"label": "Email Address",
"description": "This email will be used for notification purposes only"
}
{
"type": "pathfield",
"name": "./backgroundImage",
"label": "Background Image",
"description": "Recommended size: 1920x1080px. Supports JPG, PNG, and WebP formats.",
"rootPath": "/content/dam"
}
{
"type": "numberfield",
"name": "./animationDuration",
"label": "Animation Duration",
"description": "Duration in milliseconds. Lower values = faster animations.",
"min": 100,
"max": 5000,
"defaultValue": 1000
}
`
The description property:
- Adds fieldDescription attribute in AEM
- Appears as gray text below the field label
- Helps authors understand field purpose and constraints
- Supports special characters (automatically escaped)
When to Use:
- Explaining technical fields (CSS classes, regex patterns)
- Providing examples or format requirements
- Clarifying business rules or constraints
- Guiding authors on best practices
Pro Tip: Combine with contextualHelp for comprehensive guidance:
`json
{
"type": "textfield",
"name": "./regex",
"label": "Validation Pattern",
"description": "JavaScript regex pattern for validation",
"contextualHelp": {
"text": "Enter a valid JavaScript regular expression",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"
}
}
`
$3
Limit the number of characters users can enter using the maxLength property:
`json
// Short text field
{
"type": "textfield",
"name": "./title",
"label": "Title",
"maxLength": 50,
"placeholder": "Maximum 50 characters"
}
// Textarea with character limit
{
"type": "textarea",
"name": "./description",
"label": "Description",
"maxLength": 500,
"rows": 5,
"description": "Maximum 500 characters"
}
// Product code
{
"type": "textfield",
"name": "./sku",
"label": "SKU",
"maxLength": 12,
"placeholder": "12-char code"
}
`
The maxLength property:
- Adds maxlength attribute to the field
- Prevents users from entering more than the specified number of characters
- Browser-native validation (no form submission required)
- Works with textfield, textarea, and pathfield types
Common Use Cases:
`json
// Tweet-length content
{ "maxLength": 280 }
// Meta descriptions for SEO
{ "maxLength": 160 }
// Headline/Title fields
{ "maxLength": 100 }
// Product codes or IDs
{ "maxLength": 20 }
`
Best Practices:
- Always inform users about the limit (via description or placeholder)
- Consider UX: extremely restrictive limits can frustrate authors
- Use in combination with required validation for data quality
- Common limits: 50 (titles), 160 (meta), 500 (short descriptions), 2000 (long content)
$3
Provide placeholder text using the emptyText property (Coral UI native alternative to placeholder):
`json
// Search field
{
"type": "textfield",
"name": "./search",
"label": "Search",
"emptyText": "Type to search..."
}
// Email with format example
{
"type": "textfield",
"name": "./email",
"label": "Email",
"emptyText": "example@domain.com"
}
// Date format hint
{
"type": "textfield",
"name": "./eventDate",
"label": "Event Date",
"emptyText": "MM/DD/YYYY"
}
`
The emptyText property:
- Native Coral UI property (semantic alternative to placeholder)
- Displays hint text in empty fields
- Disappears when user starts typing
- If both placeholder and emptyText are specified, emptyText takes precedence
Difference from placeholder:
- placeholder β Converted to emptyText in AEM (for backward compatibility)
- emptyText β Direct Coral UI property (more semantic)
- Both achieve the same visual result
- Use emptyText for new implementations, placeholder for familiarity
Common Patterns:
`json
// Format hints
{ "emptyText": "YYYY-MM-DD" }
{ "emptyText": "(555) 123-4567" }
// Action prompts
{ "emptyText": "Start typing..." }
{ "emptyText": "Select an option" }
// Examples
{ "emptyText": "e.g., John Doe" }
{ "emptyText": "e.g., /content/mysite/en" }
`
Pro Tip: For complex format requirements, combine with description or validation:
`json
{
"type": "textfield",
"name": "./phone",
"label": "Phone Number",
"emptyText": "(555) 123-4567",
"description": "US phone numbers only",
"validation": {
"pattern": "^\\(\\d{3}\\) \\d{3}-\\d{4}$",
"message": "Please use format: (555) 123-4567"
}
}
`
`
$3
Assign custom IDs to fields for JavaScript integration and specific styling using the graniteId property:
`json
// Custom field ID for JavaScript hooks
{
"type": "select",
"name": "./contentType",
"label": "Content Type",
"graniteId": "content-type-selector",
"options": [...]
}
// For dynamic field manipulation
{
"type": "textfield",
"name": "./dynamicField",
"label": "Dynamic Field",
"graniteId": "js-dynamic-field"
}
// Multiple fields with coordinated IDs
{
"type": "checkbox",
"name": "./enableAdvanced",
"label": "Enable Advanced",
"graniteId": "advanced-toggle",
"cqShowHide": true,
"showhideTarget": ".advanced-options"
},
{
"type": "numberfield",
"name": "./advancedValue",
"label": "Advanced Value",
"graniteId": "advanced-input",
"showhideClass": "advanced-options"
}
`
The graniteId property:
- Adds granite:id attribute to the field
- Provides stable, predictable IDs for JavaScript selectors
- Useful for custom client libraries and interactions
- Better than relying on generated or name-based IDs
Common Use Cases:
- Custom JavaScript validation or formatting
- Integration with third-party libraries
- Dynamic field behavior (calculations, cascading dropdowns)
- Automated testing with stable selectors
- CSS styling for specific fields
Best Practices:
`json
// Use descriptive, kebab-case IDs
{ "graniteId": "hero-title-input" }
{ "graniteId": "primary-cta-link" }
// Prefix by component or feature
{ "graniteId": "carousel-slide-count" }
{ "graniteId": "video-autoplay-toggle" }
// Avoid generic names
// β Bad: "field1", "input", "select"
// β
Good: "product-sku", "author-bio", "featured-image"
`
$3
Add analytics tracking identifiers to fields using the trackingFeature property:
`json
// Track field usage in Adobe Analytics
{
"type": "select",
"name": "./template",
"label": "Template Selection",
"trackingFeature": "template-selector",
"options": [...]
}
// Track feature toggles
{
"type": "checkbox",
"name": "./enableVideo",
"label": "Enable Video Background",
"trackingFeature": "video-background-toggle"
}
// Track specific component interactions
{
"type": "pathfield",
"name": "./ctaLink",
"label": "CTA Link",
"trackingFeature": "hero-cta-link",
"rootPath": "/content"
}
`
The trackingFeature property:
- Adds trackingFeature attribute for analytics frameworks
- Enables tracking of component configuration patterns
- Helps identify which features are most used by authors
- Integrates with Adobe Analytics or custom tracking solutions
Enterprise Use Cases:
`json
// Component adoption tracking
{
"trackingFeature": "layout-grid-usage"
}
// Feature flag analysis
{
"trackingFeature": "personalization-enabled"
}
// Content strategy insights
{
"trackingFeature": "video-content-type"
}
// A/B testing configurations
{
"trackingFeature": "variation-b-selected"
}
`
Analytics Dashboard Examples:
- Most configured component features
- Template selection trends
- Feature adoption rates
- Author workflow patterns
$3
Conditionally hide fields in the UI while preserving their functionality using the renderHidden property:
`json
// Hidden until condition met
{
"type": "textfield",
"name": "./apiKey",
"label": "API Key",
"renderHidden": true,
"description": "Only shown to administrators"
}
// Programmatically revealed field
{
"type": "numberfield",
"name": "./advancedSetting",
"label": "Advanced Setting",
"renderHidden": true
}
// Combined with conditional logic
{
"type": "select",
"name": "./mode",
"label": "Mode",
"options": [
{ "value": "simple", "text": "Simple" },
{ "value": "advanced", "text": "Advanced" }
]
},
{
"type": "textfield",
"name": "./advancedConfig",
"label": "Advanced Config",
"renderHidden": true,
"description": "Revealed when Advanced mode is selected"
}
`
The renderHidden property:
- Adds renderHidden="{Boolean}true" attribute
- Hides field in dialog UI but keeps it in DOM
- Different from type: "hidden" (which never renders)
- Can be shown/hidden dynamically with JavaScript
- Field still submits its value when form is saved
Difference from type: "hidden":
| Feature | renderHidden: true | type: "hidden" |
|---------|---------------------|------------------|
| In DOM | β
Yes | β No |
| Can be revealed | β
Yes | β No |
| Label shown | β No (while hidden) | β Never |
| Use case | Conditional UI | Always hidden data |
Common Patterns:
`json
// Admin-only fields
{
"renderHidden": true,
"graniteId": "admin-field",
"description": "Revealed for admin users via JS"
}
// Progressive disclosure
{
"renderHidden": true,
"className": "expert-mode-field"
// Shown when "Expert Mode" is enabled
}
// Feature flag controlled
{
"renderHidden": true,
"description": "Beta feature, hidden until flag enabled"
}
`
$3
Make fieldsets and containers collapsible to organize long dialogs using the collapsible property:
`json
// Collapsible fieldset
{
"type": "fieldset",
"name": "advancedSettings",
"label": "Advanced Settings",
"collapsible": true,
"fields": [
{ "type": "textfield", "name": "./customClass", "label": "Custom CSS Class" },
{ "type": "numberfield", "name": "./zIndex", "label": "Z-Index" },
{ "type": "checkbox", "name": "./lazyLoad", "label": "Lazy Load" }
]
}
// Collapsible container
{
"type": "container",
"name": "seoOptions",
"collapsible": true,
"fields": [
{ "type": "textfield", "name": "./metaTitle", "label": "Meta Title", "maxLength": 60 },
{ "type": "textarea", "name": "./metaDescription", "label": "Meta Description", "maxLength": 160 }
]
}
// Multiple collapsible sections
{
"type": "fieldset",
"name": "styling",
"label": "Styling Options",
"collapsible": true,
"fields": [
{ "type": "colorfield", "name": "./backgroundColor", "label": "Background Color" },
{ "type": "colorfield", "name": "./textColor", "label": "Text Color" }
]
},
{
"type": "fieldset",
"name": "animation",
"label": "Animation Settings",
"collapsible": true,
"fields": [
{ "type": "select", "name": "./effect", "label": "Effect", "options": [...] },
{ "type": "numberfield", "name": "./duration", "label": "Duration (ms)" }
]
}
`
The collapsible property:
- Adds collapsible="{Boolean}true" attribute
- Renders a collapse/expand toggle icon
- Helps organize long dialogs into manageable sections
- Users can collapse sections they don't need
- Works with fieldset and container types
Best Practices:
``json