Skip to content

Components ​

All from @clipless/react. Conditions (renderIf, showIf, requiredIf, disabledIf, condition) use the syntax in Concepts.

<Workflow> ​

The root. Holds the session and renders the stage the viewer owns.

Prop
idthe workflow id you deploy under, e.g. expense_claim_v1
versionyour label for this version (informational)
runtimeTokena session token from your backend — connects to the engine
refreshToken() => Promise<string>: a new token for the same session and role, from your backend. Renews before expiry and on a refusal; see Getting started
serverUrlthe engine's base URL (with runtimeToken); default https://api.clipless.dev — only set it for a local engine
onCompletecalled with the answers when the session completes
onStoreReadyreceives the underlying store (devtools, tests)
componentRegistryreplace default field components by type: { date: MyDatePicker }

It always runs against the engine: without a runtimeToken it has no session to render. Prefill and answers carried from an earlier run are given when the session starts (prefill, prefillFrom in the API).

<Stage> ​

Prop
namestage id
actorthe owning role, e.g. role:manager — a string literal
labeldisplay name
renderIfcondition for the stage to be part of the path; otherwise it is skipped
transitions[{ targetStageId, condition? }] — where to go on completion; see Loop-backs
freshstart every round empty (a review decides again)
visibleToroles that may read its answers besides the owner; default everyone
mode'live' (default), 'readonly' or 'replay' — the latter two refuse answers

Renders its children only when it is the active stage and the viewer owns it.

<Step> ​

Prop
namestep id
labeldisplay name (for your stepper)
showIfcondition to include the step

Fields directly inside a <Stage> without a <Step> go into one implicit step.

<Field> ​

Prop
namefield id
typeone of the types below (string is text)
label, placeholder
required, requiredIfmust be answered (always / when a condition holds)
mustBeTruea checkbox that passes only when ticked; implies required
showIf, disabledIfhidden fields are skipped; disabled ones are not validated
min, max, minLength, maxLength, pattern, stepconstraints
options[{ value, label? }] for select, radio, multicheck, ranking, matrix columns
multiple, minSelect, maxSelect, rowsselection and matrix settings
format, mask, maskedinput masks (ssn, phone, zip, ein, …) and hide-on-blur
accept, maxSizefile fields: allowed types (.pdf,image/*) and bytes — see File uploads
vaultencrypt at rest with the session's key
piipersonal data: kept out of caches, masked in developer tools
visibleToroles that may read it; 'everyone' re-opens a field of a restricted stage
carryOver={false}never carried into a new run of a recurring workflow

Types: text textarea email tel url number currency date datetime time yearmonth select radio checkbox multicheck boolean yesno rating tags address georanking matrix signature file hidden custom.

Your own input ​

With no children, a field renders the default component for its type. Otherwise, children can be:

tsx
{/* an element: value, onChange, onBlur, name are wired in */}
<Field name="email" type="email"><input className="my-input" /></Field>

{/* a component that reads the field itself */}
<Field name="email" type="email"><MyEmailInput /></Field>
function MyEmailInput() {
  const f = useField() // value, onChange, onBlur, error, touched, required, disabled, isReadOnly
  return <TextField {...f.bindings} error={f.error} />
}

{/* a render function */}
<Field name="email" type="email">{(f) => <TextField {...f.bindings} />}</Field>

<FieldGroup> ​

A repeatable group — its value is a list of items.

tsx
<FieldGroup name="dependents" minItems={0} maxItems={5}>
  {({ id }) => (
    <>
      <Field name="name" type="text" required />
      <Field name="age" type="number" />
    </>
  )}
</FieldGroup>

useFieldGroup('dependents') gives { items, add, remove, canAdd, canRemove } for your buttons. Each item has its own fields, and the engine validates every item against that item's answers. A showIf inside an item reads its siblings, such as a student question asked only of a dependent whose relationship is Child. minItems and maxItems are checked on submit. The tax return in the playground has dependents and businesses, with collapsed previews.

<Action> ​

Declared inside the <Stage> or <Field> that triggers it. See Actions for every type.

Prop
idthe name its result is stored under (useAction(id), conditions)
typewebhook email otp_send otp_verify ai_prompt kyc pdf
onin a stage: "enter", or by default when it is submitted. In a field: "valid" (default), "change", "blur", "enter_key" — see Field actions
blockinga failure stops the stage from completing
visibleToroles that may read its result
timeoutSecondshow long it may wait for an outcome (default 24 h; 20 min for ai_prompt)
sendotp_verify: the otp_send whose code it checks (needed when there are several)
…the type's own settings

<Deadline> ​

A clock on the stage it sits in. It starts when the stage is entered. See Deadlines.

Prop
after30m, 4h, 2d, or 3bd (business days, in the session's time zone)
goToleave the stage unsubmitted and move to this stage
expireend the session (status expired)
children<Action>s to run when it passes (a reminder); never blocking, no on

<Remarks> ​

Notes on a field, kept beside the answer rather than in it. A remark can explain an answer, or be a note one reviewer leaves for another. The engine accepts a remark only where the active stage declares a <Remarks> for that field.

Try it

An expense claim with remarks on the amount in the playground runs in your browser. Play every role from the toolbar.

tsx
// The employee explains their answer. Anyone who can read `amount` reads the remark.
<Field name="amount" type="number">
  <Remarks />
</Field>

// The manager's notes on the employee's amount, for finance only
<Stage name="review" actor="role:manager">
  <Remarks on="amount" visibleTo={['role:finance']} />
</Stage>
Prop
onthe field (optional inside a <Field>). It can be any stage's top-level field that the stage's owner can read
visibleTowho reads these remarks besides their author. Default: whoever can read the field
labelheading for the default UI
children(remarks) => ReactNode renders your own UI (see useRemarks)

The default UI is a comment icon on the field, showing how many remarks there are. Clicking it opens a popup with the thread this viewer can read and, while they're allowed to add, a box to write a new remark. Inside a <Field> the icon sits at the end of the label row. With on, it appears wherever you place the tag, such as beside the value in a summary of an earlier stage's answers. Each remark is a remark event in the ledger, stamped with the stage it was left from. That stage's visibleTo decides who receives it. Deploy refuses a <Remarks> on a field that doesn't exist or that the stage's owner can't read.

Remarks are not encrypted

A remark is stored as plain text, even on a vault or pii field. Don't ask for personal data in one. Declare <Remarks> where people explain or discuss an answer, not where they would repeat it.