Portal Community

The Core Idea

Many numeric fields have common values that users pick most of the time, plus occasional custom values. Forcing users to type common values is slow; a pure dropdown prevents custom values. Quick-Pick solves this with two controls sharing one binding path:

  1. Select control — offers preset values as fast-click options
  2. Number control — allows any value to be typed directly

Because both controls bind to the same path, changing one immediately updates the other. Whoever wrote last is the authoritative value.

Basic Implementation — Duration Field

// Both controls share binding.path: "task.durationMinutes"

// Control 1: Preset quick-picks
{
  "id": "duration-preset",
  "type": "select",
  "label": "Duration (preset)",
  "width": "half",
  "order": 1,
  "settings": {
    "clearable": true,
    "placeholder": "Quick pick...",
    "options": [
      { "value": 15, "label": "15 minutes" },
      { "value": 30, "label": "30 minutes" },
      { "value": 60, "label": "1 hour" },
      { "value": 90, "label": "1.5 hours" },
      { "value": 120, "label": "2 hours" },
      { "value": 480, "label": "Full day (8h)" }
    ]
  },
  "binding": {
    "source": "$json",
    "path": "task.durationMinutes"
  }
},

// Control 2: Manual override
{
  "id": "duration-manual",
  "type": "number",
  "label": "Duration (minutes)",
  "width": "half",
  "order": 2,
  "settings": {
    "min": 1,
    "max": 1440,
    "step": 5,
    "suffix": "min",
    "placeholder": "Or type any value"
  },
  "binding": {
    "source": "$json",
    "path": "task.durationMinutes"
  }
}

How Last-Write-Wins Works

The FormEngine treats both controls as writing to the same field. When the user selects "1 hour" from the dropdown, the engine sets task.durationMinutes = 60 and the number input immediately shows 60. When the user then types 75 in the number input, the engine sets task.durationMinutes = 75 and the dropdown de-selects (no matching option).

No Special Configuration Required The quick-pick pattern is not a special control type — it is a composition of two standard controls sharing a binding path. No extra settings or flags are needed. The FormEngine handles the synchronisation automatically.

Quick-Pick for Currency Amount

// Shared path: "payment.amount"
{
  "id": "amount-preset",
  "type": "select",
  "label": "Amount",
  "width": "third",
  "order": 1,
  "settings": {
    "placeholder": "Common amounts",
    "options": [
      { "value": 25, "label": "$25" },
      { "value": 50, "label": "$50" },
      { "value": 100, "label": "$100" },
      { "value": 250, "label": "$250" },
      { "value": 500, "label": "$500" }
    ]
  },
  "binding": { "source": "$json", "path": "payment.amount" }
},
{
  "id": "amount-custom",
  "type": "number",
  "label": "Custom Amount",
  "width": "third",
  "order": 2,
  "settings": {
    "format": "currency",
    "currency": "USD",
    "min": 1,
    "prefix": "$"
  },
  "binding": { "source": "$json", "path": "payment.amount" }
}

Quick-Pick for Text Values

The pattern works with text fields too. A common example is an email subject line with templates plus a custom option:

{
  "id": "subject-preset",
  "type": "select",
  "label": "Email Subject",
  "width": "half",
  "settings": {
    "clearable": true,
    "placeholder": "Use a template...",
    "options": [
      { "value": "Invoice #{{invoiceId}} from ACME Corp", "label": "Invoice notification" },
      { "value": "Your order has shipped", "label": "Order shipped" },
      { "value": "Action required: Please review", "label": "Action required" }
    ]
  },
  "binding": { "source": "$json", "path": "email.subject" }
},
{
  "id": "subject-custom",
  "type": "text",
  "label": "Or type a custom subject",
  "width": "half",
  "settings": { "maxLength": 150 },
  "binding": { "source": "$json", "path": "email.subject" }
}

Validation on the Shared Path

Apply validation to one of the two controls — whichever is the "primary" control in your mental model. The validation engine evaluates the actual field value regardless of which control wrote it:

// Apply required validation to the manual override control
// (it has the same binding.path, so the FormEngine validates the shared value)
{
  "id": "duration-manual",
  "type": "number",
  "label": "Duration (minutes)",
  "validation": {
    "required": true,
    "min": 1,
    "max": 1440
  },
  "binding": { "source": "$json", "path": "task.durationMinutes" }
}

Layout Recommendations

PatternSelect WidthNumber/Text WidthCombined Width
Full-row durationhalfhalffull row
Compact amountthirdthirdtwo-thirds
Subject linehalfhalffull row
UX Tip Label the select control with a hint like "Quick pick..." or "Common values" and the text/number control with "Or enter custom value". This communicates the dual-input pattern to users immediately.