FormFlow
Task-based steps, draft continuity, validation and reversible navigation before commit.
Use the web element directly without the semantic runtime.
Use this component directly or compose it in application code.
The standard recipe resolver does not select this component automatically.
Import and live example#
A two-step identity/review flow with a slotted field for each step.
Interactive preview requires JavaScript.
View the complete example source
import {registerAeliqoElements} from "@varendo/web";
import {
AeliqoBreakdownElement,
AeliqoComparisonElement,
AeliqoExplorerElement,
AeliqoFormFlowElement,
AeliqoInvestigationElement,
AeliqoQualityPanelElement,
AeliqoRecordEditorElement,
AeliqoSearchResultsElement,
} from "@varendo/web/compound";
import {AeliqoDateRangeElement, AeliqoTextFieldElement} from "@varendo/web/inputs";
import type {ResultRef, Scalar} from "@varendo/core";
import type {VisualizationBindingContext, VisualizationSpec} from "@varendo/core/visualization";
import type {AeliqoDataColumn, AeliqoDataScope, AeliqoFieldOption} from "@varendo/web/data";
import type {VisualizationDataset} from "@varendo/web/visualization";
const catalogRef: ResultRef = {
id: "aeliqo-catalog-example",
revision: "1",
outputId: "people",
queryDigest: "catalog-query",
scopeDigest: "catalog-scope",
};
const catalogRows: readonly Readonly<Record<string, Scalar>>[] = [
{id: "ada", name: "Ada Lovelace", team: "Research", date: "2026-09-08", amount: 120},
{id: "lin", name: "Lin Chen", team: "Product", date: "2026-09-09", amount: 96},
{id: "grace", name: "Grace Hopper", team: "Research", date: "2026-09-10", amount: 144},
];
const catalogColumns: readonly AeliqoDataColumn[] = [
{key: "id", label: "ID", type: "text", sortable: true},
{key: "name", label: "Name", type: "text", sortable: true},
{key: "team", label: "Team", type: "text"},
{key: "date", label: "Date", type: "date", sortable: true},
{key: "amount", label: "Amount", type: "integer", align: "end"},
];
const catalogFields: readonly AeliqoFieldOption[] = [
{id: "name", label: "Name", type: "text"},
{id: "team", label: "Team", type: "text"},
{id: "amount", label: "Amount", type: "integer"},
];
const catalogScope: AeliqoDataScope = {loaded: catalogRows.length, filteredTotal: catalogRows.length, populationDigest: catalogRef.scopeDigest, kind: "filtered", label: "Authorized people"};
const catalogVisualizationContext: VisualizationBindingContext = {results: []};
const catalogVisualizationDataset: VisualizationDataset = {result: catalogRef, rows: catalogRows};
const catalogVisualizationSpecs: Readonly<Record<"trend", VisualizationSpec>> = {trend: {version: "1", view: "trend", plot: {version: "1", root: {kind: "unit", mark: "line", result: catalogRef, missing: "gap", encoding: {x: {field: "date", scale: "temporal"}, y: {field: "amount", scale: "linear"}}}}}};
registerAeliqoElements();
const host = document.querySelector<HTMLElement>("#aeliqo-example") ?? (() => {
const created = document.createElement("div");
created.id = "aeliqo-example";
(document.body ?? document.documentElement).append(created);
return created;
})();
function createCatalogRoot(container: HTMLElement): HTMLElement {
const root = document.createElement("div");
root.dataset.catalogSourceRoot = "true";
root.style.display = "grid";
root.style.gap = "12px";
container.append(root);
return root;
}
function createCatalogElement<T extends HTMLElement>(tagName: string, container: HTMLElement): T {
const element = document.createElement(tagName) as T;
element.setAttribute("data-aeliqo-theme", "light");
container.append(element);
return element;
}
function appendSlottedText(root: HTMLElement, slot: string, textContent: string): void {
const content = document.createElement("span");
content.slot = slot;
content.textContent = textContent;
root.append(content);
}
const root = createCatalogRoot(host);
(((root) => {
const element = createCatalogElement<AeliqoFormFlowElement>("aeliqo-form-flow", root);
element.steps = [{
id: "identity",
label: "Identity",
fieldNames: ["name"]
}, {
id: "review",
label: "Review",
fieldNames: ["period"]
}];
element.activeStep = "identity";
element.draft = { name: "Ada Lovelace" };
const identity = document.createElement("div");
identity.slot = "step-identity";
const name = document.createElement("aeliqo-text-field") as AeliqoTextFieldElement;
name.label = "Name";
name.name = "name";
name.value = "Ada Lovelace";
identity.append(name);
const review = document.createElement("div");
review.slot = "step-review";
review.textContent = "Review the authorized values before commit.";
element.append(identity, review);
}))(root);
Purpose#
FormFlow walks users through named steps while retaining field drafts across navigation and
validating each step before commit. The host supplies draft and activeStep and owns durable
persistence.
When to use it#
- A task splits into named steps users move through forward and back.
- Each step must validate before the next step or the commit runs.
- Draft values must carry across steps and keep focus stable.
- Commit is a host proposal; the flow never applies the effect itself.
When to use a different component#
- Use Form when every field fits on one screen.
- Use RecordEditor when save must carry an existing record's key and revision.
- Use Dialog for a short confirmation, not a stepped task.
Properties and defaults#
Draft and validation are host-visible; step transitions and commit are typed requests with preserved focus.
| Property | Use and ownership | Declared type | Initial value |
|---|---|---|---|
steps | Supplies the ordered form-flow steps with stable IDs and labels. | readonly AeliqoFormFlowStep[] | [] |
activeStep | Supplies active step to the component as application-owned input. | string | '' |
draft | Supplies the current multi-step form draft. The host remains the source of truth across steps. | Readonly<Record<string, FormDraftValue>> | {} |
validation | Supplies authoritative validation results for the current form-flow draft. | Readonly<Record<string, string | undefined>> | {} |
status | Supplies the explicit ready, loading, empty, partial, stale, or error state. | AeliqoCompoundStatus | 'ready' |
message | Provides the user-facing status, error, or recovery message. | string | '' |
title | Provides the visible title for the current data or compound surface. | string | 'Form' |
nextLabel | Provides the visible text for the next control or section. | string | 'Next' |
backLabel | Provides the visible text for the back control or section. | string | 'Back' |
commitLabel | Provides the visible text for the commit control or section. | string | 'Commit' |
Events#
| Event | Payload | Emitted | Application responsibility |
|---|---|---|---|
aeliqo-form-flow-step | The proposed next step ID and current draft. | After Back or Next is activated. | Validate the draft and update activeStep if the transition is accepted. |
aeliqo-form-flow-commit | The current draft, validation evidence, and step context. | After the user commits the final valid step. | Authorize and execute the registered action; report pending, success, or failure. |
Events cross the component boundary as user proposals. Validate their detail and current authority before applying a business effect.
Listen for aeliqo-form-flow-step
const element = document.querySelector('aeliqo-form-flow');<p>element?.addEventListener('aeliqo-form-flow-step', (event) => {
const proposal = (event as CustomEvent<unknown>).detail;
// Validate the proposal, update host state, then assign controlled properties.
applyProposal(proposal);
});Listen for aeliqo-form-flow-commit
const element = document.querySelector('aeliqo-form-flow');</p>
<p>element?.addEventListener('aeliqo-form-flow-commit', (event) => {
const proposal = (event as CustomEvent<unknown>).detail;
// Validate the proposal, update host state, then assign controlled properties.
applyProposal(proposal);
});States and failure handling#
Relevant states:
- ready
- invalid
- pending
- error
- disabled
Expected result: The flow preserves drafts and focus while emitting step or commit proposals after validation.
Keyboard, focus, and accessibility#
Keyboard behavior:
- Tab follows current step
- Enter on next/commit
- Arrow keys within controls
- focus moves to new step
Exposed semantics:
- aria-current
- aria-label
- button
- li
- ol
- role
- section
Responsive behavior#
Keep the step label, validation, draft, and navigation in DOM order. Let the flow become one column as space decreases.
Style hooks#
- Shadow parts:
header,meta,navigation,root,status,step,step-list,step-panel - Varendo design tokens referenced:
--aeliqo-space-16,--aeliqo-space-8
Related components#
Generated TypeScript declaration#
View generated declaration
export declare class AeliqoFormFlowElement extends AeliqoCompoundElement {
static readonly properties: {
steps: {
attribute: boolean;
};
activeStep: {
attribute: string;
type: StringConstructor;
};
draft: {
attribute: boolean;
};
validation: {
attribute: boolean;
};
status: {
type: StringConstructor;
};
message: {
type: StringConstructor;
};
title: {
type: StringConstructor;
};
nextLabel: {
attribute: string;
type: StringConstructor;
};
backLabel: {
attribute: string;
type: StringConstructor;
};
commitLabel: {
attribute: string;
type: StringConstructor;
};
};
static readonly styles: import("lit").CSSResult[];
steps: readonly AeliqoFormFlowStep[];
activeStep: string;
draft: Readonly<Record<string, FormDraftValue>>;
validation: Readonly<Record<string, string | undefined>>;
status: AeliqoCompoundStatus;
message: string;
title: string;
nextLabel: string;
backLabel: string;
commitLabel: string;
private transientDraft;
private transientDraftSource;
private pendingFocus;
private focusStepAfterUpdate;
protected willUpdate(changes: PropertyValues): void;
protected updated(changes: PropertyValues): void;
protected render(): import("lit").TemplateResult<1>;
private renderStepStatus;
private hasNamedStepSlots;
private controlsForStep;
private stepValid;
private collectDraft;
private resetTransientDraft;
private replaceCurrentValue;
private collectControlDraft;
private skipDraftControl;
private collectToggleDraft;
private collectMultipleSelectDraft;
private collectDateRangeDraft;
private captureDraft;
private moveRelative;
private moveTo;
private stepTransition;
private canMove;
private stepsValidBetween;
private dispatchStepChange;
private readonly commit;
}Version#
This page documents the Varendo 0.6 component contract.
