10.2 Form Validations, Error Handling & Dynamic Display
Key Takeaways
Form validations in Appian SAIL are declarative: the validations parameter expects null or an empty list {} to pass, or a text string (or list of strings) to trigger an error banner and block submission.
Validations exist at three distinct hierarchical scopes: Component-level (formatting/data constraints), Section-level (multi-field logical dependencies), and Form-level (global aggregate constraints).
The validate: true parameter on action buttons enforces validation before completing a task; secondary buttons (such as 'Cancel' or 'Save Draft') must explicitly configure validate: false to prevent blocking submission on invalid or empty fields.
showWhen conditionally includes or omits components from the evaluated interface tree; omitting a component from display does NOT clear its underlying variable value in memory.
The critical exam distinction between readOnly and disabled: readOnly renders data cleanly as static accessible display text without input borders, whereas disabled renders an inactive, greyed-out interactive input control.
Form Validations, Error Handling & Dynamic Display
Quick Summary: In Appian SAIL, form validation and dynamic visibility are managed declaratively through standardized component parameters. The
validationsparameter evaluates at component, section, and form levels, halting workflow submission when text error messages are produced. Developers govern task completion using thevalidateparameter on buttons, control interface rendering conditionally usingshowWhen, and precisely differentiate between clean display data (readOnly) and temporarily inactive form controls (disabled).
The Declarative Validation Engine in Appian SAIL
In traditional web frameworks, form validation often requires coordinating complex client-side JavaScript regex listeners with asynchronous backend validation endpoints. Appian SAIL abstracts this complexity through a declarative, server-evaluated validation engine.
Supported input components and appropriate layouts provide a validations parameter. The validation contract is simple and universal:
+-----------------------------------------------------------------------------+
| SAIL VALIDATION EVALUATION CONTRACT |
+-----------------------------------------------------------------------------+
| Evaluated Expression Result | System Validation Behavior |
| --------------------------------+------------------------------------------|
| null or {} (Empty Array) | PASS: Validation succeeds. |
| | No error displayed; submission permitted.|
| --------------------------------+------------------------------------------|
| "Error message string" | FAIL: Validation fails. |
| or {"Error 1", "Error 2"} | Displays red error text beneath field. |
| | Blocks form submission if validate: true |
+-----------------------------------------------------------------------------+
Built-in Validation Parameters
In addition to the custom validations expression parameter, SAIL components provide built-in validation shorthand:
required: true: Automatically marks the field with a red required asterisk (*) and validates required input according to the component contract; trim text explicitly if whitespace-only values must be rejected. If empty upon submission, it generates a standard error message.requiredMessage(Text): Customizes the default required error message (e.g.,"Please provide your legal business name before proceeding.").- Date boundaries: Use custom validations to compare entered dates against the business limits. Do not invent minDate or maxDate parameters for date components.
Validation Scopes: Component, Section & Form-Level Validations
Appian categorizes validations into three distinct architectural scopes based on where the error message should appear and what data dependencies are involved.
+-----------------------------------------------------------------------------+
| HIERARCHICAL VALIDATION SCOPES |
+-----------------------------------------------------------------------------+
| |
| 1. a!formLayout(validations: ...) |
| [Global Form Banner: "Total allocation percentages must equal 100%"] |
| │ |
| ├── 2. a!sectionLayout(validations: ...) |
| │ [Section Banner: "End Date must occur after Start Date"] |
| │ │ |
| │ └── 3. a!textField(validations: ...) |
| │ [Inline Field Error: "Enter a valid corporate email"] |
+-----------------------------------------------------------------------------+
1. Component-Level Validations
Component validations are attached directly to atomic input fields (a!textField, a!integerField, a!dropdownField). They evaluate constraints specific to that single piece of data.
- Error Placement: Renders directly beneath the input field with a red border around the input box.
- Common Use Cases: Regex pattern matching (email formatting, phone numbers, tax IDs), range boundaries (age >= 18), or character length restrictions.
a!textField(
label: "Corporate Email Address",
value: ri!employee.email,
saveInto: ri!employee.email,
required: true,
validations: if(
a!isNullOrEmpty(ri!employee.email),
null,
if(
like(ri!employee.email, "*@acme.com"),
null,
"Email address must belong to the corporate domain (@acme.com)."
)
)
)
2. Section-Level Validations
Attached to a!sectionLayout(validations: ...). Used when a validation rule depends on relationships between multiple fields grouped inside that section.
- Error Placement: Renders as a prominent callout message at the bottom or top of the section container.
- Common Use Cases: Cross-field comparisons where attributing the error to a single field is ambiguous (e.g., ensuring
startDateprecedesendDate, or verifying that at least one of "Mobile Phone" or "Home Phone" is provided).
a!sectionLayout(
label: "Contract Performance Period",
contents: {
a!dateField(label: "Start Date", value: ri!startDate, saveInto: ri!startDate),
a!dateField(label: "End Date", value: ri!endDate, saveInto: ri!endDate)
},
validations: if(
and(a!isNotNullOrEmpty(ri!startDate), a!isNotNullOrEmpty(ri!endDate)),
if(ri!endDate < ri!startDate, "The contract End Date cannot be earlier than the Start Date.", null),
null
)
)
3. Form-Level Validations
Attached to the root a!formLayout(validations: ...). Used for aggregate calculations or global workflow conditions spanning multiple disparate sections.
- Error Placement: Displays at the top of the form layout directly beneath the form title.
- Common Use Cases: Verifying that a budget distribution table totals exactly 100%, or confirming that at least one line item has been entered across multiple tabs.
| Attribute | Component Validation | Section Validation | Form-Level Validation |
|---|---|---|---|
| Component | a!textField(), a!dropdownField(), etc. | a!sectionLayout() | a!formLayout() |
| Visual Position | Immediately below the specific field | Bottom/top of the section box | Top banner of the entire form |
| Target Scope | Single field data integrity | Multi-field cross-validation | Global form-wide integrity |
| User Noticeability | High context for specific field typing | Clear context for grouped logic | High-level summary of blocking issue |
Validation Triggers & Submission Mechanics (validate: true vs. false)
Understanding when validations evaluate is a cornerstone of Appian interface design and a critical topic on the ACD101 exam:
- Real-Time Evaluation (On-Change): As a user interacts with a field (typing text, selecting a dropdown value), that component's individual
validationsexpression evaluates immediately. If invalid, the error message appears in real time. - Submit-Time Evaluation (On-Submit): When the user clicks an action button configured with
submit: true, the engine triggers a comprehensive validation sweep across all components, sections, and form-level validations on the screen.
The Role of validate: true vs. validate: false on Buttons
The validate parameter on a!buttonWidget() dictates whether the button triggers this comprehensive validation sweep:
a!buttonLayout(
primaryButtons: {
a!buttonWidget(
label: "Submit Application",
value: "SUBMIT",
saveInto: ri!buttonAction,
submit: true,
style: "SOLID",
validate: true /* Enforces all validations; blocks if errors exist */
)
},
secondaryButtons: {
a!buttonWidget(
label: "Cancel Request",
value: "CANCEL",
saveInto: ri!buttonAction,
submit: true,
style: "SOLID", color: "NEGATIVE",
validate: false /* CRITICAL: Allows immediate exit without validating */
),
a!buttonWidget(
label: "Save Draft",
value: "SAVE_DRAFT",
saveInto: ri!buttonAction,
submit: true,
validate: false /* Allows saving incomplete forms */
)
}
)
Button Submission Flowchart
===========================
User clicks an a!buttonWidget()
│
▼
Is submit == true?
├──► NO ──► Execute saveInto only; stay on screen
│
└──► YES
│
▼
Is validate == true?
├──► NO ──► Complete User Input Task immediately
│ (No validation sweep; exit cleanly)
│
└──► YES
│
▼
Execute full interface validation sweep
(Component, Section, and Form validations)
│
┌───────────────┴───────────────┐
▼ ▼
Any errors found? All validations pass?
├──► YES └──► YES
│ - Highlight fields in red - Execute saveInto
│ - Scroll to first error - Complete task
│ - BLOCK task completion - Advance process token
Partial Validation with validationGroup
In complex multi-step forms or tabbed interfaces, developers often want to validate only a subset of inputs before progressing to the next step. By tagging specific input components and the corresponding "Next" button with matching validationGroup text strings (e.g., validationGroup: "STEP_1"), Appian validates only the components belonging to that group, leaving subsequent steps unvalidated.
Conditional Display Logic with showWhen
Dynamic interfaces adapt their layout to user input—revealing additional questions, showing supplementary sections, or hiding irrelevant controls. In Appian SAIL, conditional visibility is controlled universally via the showWhen parameter.
Supported components and layouts provide showWhen (accepting a boolean expression that evaluates to true or false).
a!radioButtonField(
label: "Do you have foreign financial assets?",
choiceLabels: {"Yes", "No"},
choiceValues: {true, false},
value: local!hasForeignAssets,
saveInto: {
local!hasForeignAssets,
/* Clear child data if user switches answer back to No */
if(save!value = false, {a!save(ri!taxFiling.foreignAssetCountry, null), a!save(ri!taxFiling.foreignAssetValue, null)}, {})
}
),
a!sectionLayout(
label: "Foreign Asset Disclosures",
showWhen: local!hasForeignAssets = true,
contents: {
a!textField(
label: "Jurisdiction / Country",
value: ri!taxFiling.foreignAssetCountry,
saveInto: ri!taxFiling.foreignAssetCountry,
required: true
),
a!floatingPointField(
label: "Aggregate Value ($)",
value: ri!taxFiling.foreignAssetValue,
saveInto: ri!taxFiling.foreignAssetValue,
required: true
)
}
)
Architectural Evaluation of showWhen
- Server-Side Elimination: When
showWhenevaluates tofalse, the component is completely omitted from the server's serialized UI JSON payload. It does not exist in the client DOM (unlike CSSdisplay: none). - Validation Suppression: Components hidden via
showWhen: falsedo not fire validations. If a hidden text field hasrequired: true, but its parent section hasshowWhen: false, form submission is not blocked. - Data Retention Caveat: Hiding a component does not reset or clear the underlying variable in memory! If a user types text into a field and then toggles a radio button that hides that field, the entered data still resides in ri! or local!. Developers should explicitly clear child variables in
saveIntoif business logic requires discarded answers to benull.
Component Interaction States: readOnly vs. disabled
A frequent source of confusion—and a heavily tested concept on the ACD101 exam—is the precise distinction between setting readOnly: true versus disabled: true on SAIL input components.
| Dimension | readOnly: true | disabled: true |
|---|---|---|
| Visual Presentation | Renders cleanly as static text; removes all input boxes, borders, and dropdown arrows | Renders the standard input widget outline, but greyed-out and muted |
| Semantic Intent | Communicates that this information is informational or finalized data to be read | Communicates that this control exists but is currently unavailable or inactive |
| Tab Navigation | Typically bypassed in tab order; screen readers treat as static content | Included in DOM but disabled; keyboard users cannot interact with or focus it |
| Form Theme Integration | Blends seamlessly into review screens, summaries, and dashboards | Looks like an interactive form with deactivated switches/fields |
| Primary Use Cases | Read-only summary views, manager approval reviews, confirmation screens | Conditional inputs waiting for a prerequisite selection (e.g., State disabled until Country selected) |
VISUAL COMPARISON:
[readOnly: true] [disabled: true]
Customer Name Customer Name
Acme Corporation +-------------------------------+
(Clean text, no box; looks like prose) | Acme Corporation |
+-------------------------------+
(Grey background, bordered box;
looks like an inactive input)
Realistic Scenario: Multi-Step Wizard Form with Conditional Sections
A commercial loan origination platform utilizes a multi-step intake wizard:
- Wizard State: Controlled by
local!currentStep: 1. - Step Tracker: An
a!milestoneField()visually indicates progress across Step 1 (Borrower Info), Step 2 (Loan Request), and Step 3 (Confirmation). - Conditional Sections:
- Section 1 (
showWhen: local!currentStep = 1) collects applicant details. Its fields belong tovalidationGroup: "STEP_1". - Section 2 (
showWhen: local!currentStep = 2) collects requested loan amount and collateral details. If "Collateral Type" is "Real Estate", a nested sub-section displays deed inputs (showWhen: ri!loan.collateralType = "REAL_ESTATE"). - Section 3 (
showWhen: local!currentStep = 3) renders all previously entered data usingreadOnly: truefor executive sign-off.
- Section 1 (
- Action Buttons:
- "Next Step" button: advances step counter (
saveInto: a!save(local!currentStep, local!currentStep + 1)), setsvalidationGroup: "STEP_1", and setsvalidate: true. - "Previous Step" button: decrements step counter with
validate: false. - "Cancel Application" button: sets
validate: falseandsubmit: true, allowing the user to abort immediately. - "Final Submit" button (on Step 3): sets
submit: trueandvalidate: trueto trigger the workflow process.
- "Next Step" button: advances step counter (
Exam Traps & Validation Guidelines
Warning
Common Exam Trap 1: The 'Frozen Cancel Button' Trap. A developer creates a User Input Task with required fields. When testing, clicking "Cancel" fails to close the task and instead paints the form in red error messages. Cause: the developer forgot to set validate: false on the Cancel button. Because validate defaults to true when submit is true, clicking Cancel forces a full validation sweep that blocks submission.
Warning
Common Exam Trap 2: Returning Booleans in validations. The validations parameter expects a text string error message or null. Writing validations: ri!age >= 18 is an evaluation error. The correct expression is validations: if(ri!age < 18, "Applicant must be at least 18 years old.", null).
Tip
Dynamic Form Best Practice: Whenever hiding a dependent section using showWhen, decide whether to retain or reset the underlying dependent data inside the parent component's saveInto. Otherwise, obsolete or conflicting data will silently persist into the database.
Source checked 2026-10-18: Appian button validation behavior.
A user reports that when clicking the "Cancel Request" button on a User Input Task, the form refuses to close and instead highlights five required fields in red with error messages. What configuration change must the developer make to the Cancel button?
Set submit: false and style: "SOLID", color: "NEGATIVE" on the Cancel button.
Set showWhen: false on the required fields when the button is pressed.
Set required: false on all inputs across the entire form layout.
Set validate: false on the Cancel button's a!buttonWidget() configuration.
What is the primary functional and visual distinction between setting readOnly: true versus disabled: true on an a!textField() in Appian SAIL?
readOnly: true presents the value cleanly as static text without input borders, whereas disabled: true displays a greyed-out interactive input box that cannot be edited or focused.
readOnly: true wipes the underlying variable value to null, whereas disabled: true retains the value in memory.
readOnly: true can only be applied to dropdowns, whereas disabled: true applies exclusively to text fields.
readOnly: true prevents users from seeing the component, whereas disabled: true keeps the component fully visible.
An interface contains a section layout that should only be visible when an applicant selects "Yes" for the "Do you have prior military service?" radio button. How should this conditional visibility be implemented?
Set disabled: ri!hasPriorMilitary <> "Yes" on the section layout.
Configure showWhen: ri!hasPriorMilitary = "Yes" on the a!sectionLayout().
Wrap the section inside a process model XOR gateway condition.
Set validations: if(ri!hasPriorMilitary = "Yes", null, "Hide Section") on the section layout.
Sections you finish are checked off in the contents.