Clone on Gitea
Introduction
Explained by ducks
FAQ
Getting Started
Components
Component Basics
Auto Initialization From DOM
Templates
Children and Parents
Roles
Strictness
Fields
What is a Field
Binding Fields to DOM
Reading and Writing Fields
Value Casting
Flags
Events
Overview
Event Handlers
DOM Events
Custom Events
Propagation and Flow
Advanced Event Control
Beyond Components
States
Overview
StateEngine
DisplayStateEngine
Field Matchers
Ordering and priority
Debugging
Advanced usage
Ajax
ComponentUI
VirtualForm
Unit Testing
Helpers
Overview
Cookies
Arrays

Ordering and priority

When multiple states match at the same time, StateEngine must decide which one becomes the winner. This decision is deterministic and based on priority, specificity and rule order.

When a winner needs to be chosen, the resolution process is simple:

  1. Expand nested rules into flat states.
  2. Evaluate all when conditions.
  3. Collect all matching states.
  4. Sort them by priority.
  5. Break ties by rule order.
  6. Select the winner.

Only one state becomes the winner (unless you use eligible or overlay, which modify how additional states run) or unless you specify { active_mode="winner" } as your state engine option.

Default priority

If you never specified a priority, every state would have the same default priority. In that case, rule order determines the winner — states defined later win over earlier ones. For example:

static states = [
    [ { status: ["paid", "available"] }, (c) => c.enableShipping() ],
    [ { status: ["paid", "available"] }, (c) => c.lockOrder()      ],
];

When both conditions inevitably match, the second rule wins because it appears later, but the first is never the winner here. This is an unrealistic example, but it demonstrates the point well.

Using priority metadata

You can override ordering with explicit priority:

static states = [
    [ { status: ["paid", "available"] }, (c) => c.enableShipping(),
      { priority: 10 }
    ],
    [ { status: ["paid", "available"] }, (c) => c.lockOrder()      ],
];

Now the first rule always wins, while the second one always loses and its transitions never run. If two states have different priorities the state with the higher numeric priority wins and rule order is only used when priorities are equal.

Specificity

In many cases, you don't need explicit priority because rule order naturally encodes specificity. A more specific rule should usually appear after a more general one:

static states = [
    [ { status: "paid" }, (c) => c.enableShipping() ],

    // More specific version of "paid"
    [
        { status: "paid", total: (c, v) => v > 1000 },
        (c) => c.enablePriorityHandling()
    ]
];

If status = "paid" and total = 1500, both rules match. Because the second rule is more specific and appears later, it wins. This pattern keeps rules readable:

  • General rules first.
  • More specific refinements later.

Only reach for explicit priority when specificity cannot be expressed cleanly through ordering.

Deterministic behavior

The resolution process is fully deterministic and follows these tenets:

  • Same inputs always produce the same winner.
  • No randomness is involved.
  • Nested rules are expanded consistently.
  • Equal priority states resolve by definition order.

This is important because state transitions often control business logic, not just UI and determinism ensures that behavior is predictable and debuggable.

Practical guidance

In most components, you don't need explicit priority. Simply ordering rules from least specific to most specific is usually enough.

Use priority only when:

  • Two unrelated rules may match simultaneously.
  • You want to make resolution explicit rather than relying on order.
  • You are building more complex interaction models.

Clear ordering and occasional explicit priority are sufficient for almost all real-world components.