AEM Dialog Components #1: Textfield - Single Line Text Input
AEM Dialog Components #1: Textfield
What is a Textfield?
The Textfield is a Granite UI component that allows authors to enter single-line text in AEM component dialogs.
Common use cases:
- Titles and headings
- URLs or links
- Author name
- IDs or codes
- Email, phone
- Any short text input
Basic Configuration
Minimal XML Dialog
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"/>Essential properties:
sling:resourceType- Alwaysgranite/ui/components/coral/foundation/form/textfieldfieldLabel- Label visible to the authorname- JCR path where to save the value (always use./)
Main Properties
1. Required (Mandatory Field)
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"
required="{Boolean}true"/>Behavior: The author cannot save the dialog without filling in the field.
2. Default Value
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"
value="Default Title"/>When to use it: Provide an initial value that the author can modify.
3. Placeholder
<email
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Email"
name="./email"
emptyText="example@domain.com"/>Behavior: Shows suggestive text when the field is empty.
4. Maxlength (Maximum Length)
<shortTitle
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Short Title"
name="./shortTitle"
maxlength="{Long}50"/>Important: Use {Long} for the number type hint!
5. Validation (Regex Validation)
IMPORTANT: There is no validation="email" attribute in Granite UI. To validate email, URL or other patterns you must use granite:data with regex.
Email Validation
<email
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Email"
name="./email"
required="{Boolean}true">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
validation-regex-message="Enter a valid email address"/>
</email>Custom Regex Validation
<phoneNumber
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Phone Number"
name="./phoneNumber"
required="{Boolean}true">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^[0-9]{10}$"
validation-regex-message="Enter exactly 10 digits"/>
</phoneNumber>Note: The regex syntax varies slightly between AEM 6.3 and AEM 6.4+ for backslash escaping.
6. Disabled and ReadOnly
<!-- Disabled: disabled field, not editable -->
<id
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Component ID"
name="./id"
disabled="{Boolean}true"/>
<!-- ReadOnly: visible but not editable -->
<createdBy
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Created By"
name="./createdBy"
readOnly="{Boolean}true"/>Difference:
disabled- Grayed out field, not sent to serverreadOnly- Visually normal field but not editable
Practical Examples
Example 1: Title with Maximum Length
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"
required="{Boolean}true"
maxlength="{Long}120"
emptyText="Enter component title (max 120 chars)"/>Use case: Titles for SEO or cards with character limit.
Example 2: Email with Validation
<email
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Email Address"
name="./email"
fieldDescription="Contact email for this component"
required="{Boolean}true"
emptyText="user@example.com">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
validation-regex-message="Enter a valid email address"/>
</email>Use case: Contact forms, author info.
Example 3: URL with Validation
<externalLink
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="External Link"
name="./externalLink"
fieldDescription="Full URL including https://"
required="{Boolean}true"
emptyText="https://example.com">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$"
validation-regex-message="Enter a valid URL (http:// or https://)"/>
</externalLink>Use case: External links, API integrations.
Example 4: Custom CSS Class
<cssClass
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Custom CSS Class"
name="./cssClass"
fieldDescription="Additional CSS classes (space-separated)"
emptyText="my-class another-class"/>Use case: Allow authors to add custom CSS classes.
Example 5: Tracking/Analytics Code
<trackingId
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Tracking ID"
name="./trackingId"
fieldDescription="Google Analytics tracking ID"
required="{Boolean}true"
emptyText="UA-123456-1">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^UA-[0-9]+-[0-9]+$"
validation-regex-message="Invalid GA format (UA-XXXXXX-X)"/>
</trackingId>Use case: Analytics configuration, tracking codes.
Complete Dialog with Textfield
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Hero Component"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs">
<items jcr:primaryType="nt:unstructured">
<content
jcr:primaryType="nt:unstructured"
jcr:title="Content"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<!-- Title Field -->
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"
required="{Boolean}true"
maxlength="{Long}120"/>
<!-- Subtitle Field -->
<subtitle
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Subtitle"
name="./subtitle"
maxlength="{Long}200"
emptyText="Optional subtitle"/>
<!-- CTA Button Text -->
<ctaText
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Button Text"
name="./ctaText"
value="Learn More"
maxlength="{Long}30"/>
<!-- CTA Link -->
<ctaLink
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Button Link"
name="./ctaLink"
emptyText="/content/mysite/page"/>
</items>
</content>
</items>
</tabs>
</items>
</content>
</jcr:root>Usage in HTL
Simple Value Reading
<h1 class="hero-title">${properties.title}</h1>
<p class="hero-subtitle">${properties.subtitle}</p>
<a href="${properties.ctaLink}" class="btn">
${properties.ctaText}
</a>With Fallback and Validation
<!-- Title with fallback -->
<h1 data-sly-test="${properties.title}">
${properties.title}
</h1>
<h1 data-sly-test="${!properties.title}">
Default Title
</h1>
<!-- CTA only if link is present -->
<a data-sly-test="${properties.ctaLink}"
href="${properties.ctaLink}"
class="btn">
${properties.ctaText || 'Read More'}
</a>With Sling Model
@Model(adaptables = Resource.class)
public class HeroModel {
@ValueMapValue
private String title;
@ValueMapValue
private String subtitle;
@ValueMapValue
private String ctaText;
@ValueMapValue
private String ctaLink;
public String getTitle() {
return title != null ? title : "Default Title";
}
public String getSubtitle() {
return subtitle;
}
public String getCtaText() {
return ctaText != null ? ctaText : "Learn More";
}
public String getCtaLink() {
return ctaLink;
}
public boolean hasSubtitle() {
return subtitle != null && !subtitle.isEmpty();
}
public boolean hasCtaLink() {
return ctaLink != null && !ctaLink.isEmpty();
}
}HTL with Model:
<div data-sly-use.model="com.mysite.models.HeroModel" class="hero">
<h1 class="hero-title">${model.title}</h1>
<p data-sly-test="${model.hasSubtitle}" class="hero-subtitle">
${model.subtitle}
</p>
<a data-sly-test="${model.hasCtaLink}"
href="${model.ctaLink}"
class="hero-btn">
${model.ctaText}
</a>
</div>Advanced Properties
1. fieldDescription (Field Description)
<apiKey
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="API Key"
name="./apiKey"
fieldDescription="Enter your API key from the admin panel"
required="{Boolean}true"/>Shows help text below the field.
2. granite:class (Custom CSS)
<customField
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Custom Field"
name="./customField"
granite:class="custom-textfield-style"/>Adds custom CSS classes to the field.
3. autocomplete
<username
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Username"
name="./username"
autocomplete="username"/>Common values: username, email, name, tel, url.
Common Issues
❌ Issue 1: Value not saved
<!-- WRONG - missing ./ in name -->
<title
name="title"/>
<!-- CORRECT -->
<title
name="./title"/>Solution: Always use ./ prefix in name.
❌ Issue 2: Validation with wrong syntax
<!-- WRONG - validation="email" doesn't exist -->
<email
validation="email"
required="{Boolean}true"/>
<!-- CORRECT - use granite:data with regex -->
<email
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./email"
required="{Boolean}true">
<granite:data
jcr:primaryType="nt:unstructured"
validation-regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
validation-regex-message="Invalid email"/>
</email>Solution: There is no validation="email". Use granite:data with validation-regex.
❌ Issue 3: Maxlength ignored
<!-- WRONG - maxlength without type hint -->
<title
maxlength="50"/>
<!-- CORRECT -->
<title
maxlength="{Long}50"/>Solution: Always use {Long} for numeric values.
❌ Issue 4: Required not working
<!-- WRONG - required as string -->
<title
required="true"/>
<!-- CORRECT -->
<title
required="{Boolean}true"/>Solution: Use {Boolean}true, not string.
Best Practices
- ✅ Always use
./in name:name="./title" - ✅ Type hints for booleans and numbers:
required="{Boolean}true",maxlength="{Long}50" - ✅ EmptyText for UX: Provide examples in placeholder
- ✅ Maxlength for limits: Prevent too long inputs
- ✅ FieldDescription for complexity: Explain non-obvious fields
- ✅ Validation for critical data: Email, URL, specific patterns
- ✅ Required only if necessary: Don't force optional fields
- ✅ Useful default values: Speed up authoring
When NOT to Use Textfield
❌ Don't use textfield when:
- Multi-line text needed → Use Textarea
- Formatted text needed → Use RichText Editor
- Selection from options needed → Use Select or Radio
- AEM path selection needed → Use PathBrowser
- Number with spinner needed → Use NumberField
- Date needed → Use DatePicker
- Color needed → Use ColorField
Next Lesson
In the next lesson we'll see the Textarea component for multi-line text.
Resources:
Guide #1 of the AEM Dialog Components series. Next lesson →