rilaykit
Core concepts

Conditions System

Drive field visibility, requirement, and step navigation from live form data.

Conditions control field visibility, requirement, disabled/readonly state, and workflow step navigation. They re-evaluate live as values change.

The when() Function

when(fieldPath) returns a ConditionBuilder:

import { when } from 'rilaykit';

const condition = when('age').greaterThan(18);
condition.evaluate({ age: 25 }); // true
condition.build(); // serializable ConditionConfig, useful for logging

Operators

OperatorTrue when
equals(value) / notEquals(value)Strict (in)equality
greaterThan(n) / lessThan(n)Numeric comparison
greaterThanOrEqual(n) / lessThanOrEqual(n)Numeric comparison, inclusive
contains(value) / notContains(value)String includes substring, or array includes value
in(values) / notIn(values)Field value is (not) in the given list
matches(pattern)String matches a RegExp or pattern string
exists() / notExists()Value is (not) null/undefined

Combine with .and() / .or() — nesting groups sub-conditions:

when('userType').equals('premium')
  .and(
    when('subscription.plan').equals('pro')
      .or(when('subscription.legacy').equals(true))
  );

Field Path Resolution

Paths use dot notation for nested data: when('user.profile.age').greaterThan(18) reads data.user.profile.age. A key that exists verbatim (e.g. a repeatable composite id like items[k0].type) is matched directly before dot-path traversal.

Field-Level Conditions

Fields accept four condition keys: visible, required, disabled, readonly.

form.create(rilay, 'checkout')
  .add({
    id: 'creditCardNumber',
    type: 'text',
    props: { label: 'Credit Card Number' },
    conditions: {
      visible: when('paymentMethod').equals('credit_card'),
      required: when('paymentMethod').equals('credit_card'),
    },
  })
  .add({
    id: 'email',
    type: 'email',
    props: { label: 'Email Address' },
    conditions: {
      disabled: when('emailVerified').equals(true),
      readonly: when('accountLocked').equals(true),
    },
  });

conditions.required is enforced on its own — no validation block needed. When the condition is true and the field is empty, submit is blocked.

Step-Level Conditions (Workflows)

Steps accept visible and skippable. Reference previous steps' data with "stepId.fieldId":

const workflow = flow.create(rilay, 'user-onboarding')
  .step({
    id: 'personal-info',
    title: 'Personal Information',
    formConfig: personalInfoForm,
  })
  .step({
    id: 'payment-info',
    title: 'Payment Information',
    conditions: {
      visible: when('personal-info.planType').equals('premium')
        .or(when('company-info.employees').greaterThan(50)),
      skippable: when('personal-info.planType').notEquals('premium'),
    },
    formConfig: paymentForm,
  });

A hidden step is treated as nonexistent: navigation jumps over it and it is absent from the onComplete(data, meta) payload (meta.skippedSteps / meta.passedSteps record why).

Conditions and Validation

An invisible field is treated as nonexistent: its validation is skipped, its errors are cleared, and it never blocks submit — even if it becomes invisible mid-async-validation.

Best Practices

  • Prefer several simple conditions over one deeply nested tree.
  • Test conditions against empty, null, and undefined values — numeric operators return false for non-numbers.
  • Avoid circular dependencies between fields' conditions.

On this page