Skip to content

Sub-Forms and Composition

The real power of the UI Builder comes from composition: forms that contain controls that themselves host forms. This lets you build large, complex UIs from small, well-defined pieces — the same way a Kontakt library builder thinks in groups and zones rather than individual samples.

Two control types can embed a form or switch between forms:

TypeDescription
subFormRenders one named form inside its bounds
portalShows one of several forms based on a parameter value; supports animated transitions

The full property lists are in the Reference.

The most direct composition tool. Point it at another form by name via props.form, and it renders that form inside its bounds.

subForm control in the editor

A subForm control can supply a props.macroParameters block — macro values that apply only inside that sub-form instance. This is how you build reusable components: define the form once, instantiate it multiple times with different macros.

Example: a reusable “parameter knob” sub-form

Define param_knob.frm with a knob, label, and value display that all reference $param-id and $label-text:

# param_knob.frm
width: 60
height: 80
controls:
knob:
type: knob
anchors: { left: 5, right: 5, top: 0, height: 50 }
props:
parameterUid: $param-id
value_display:
type: parameterDisplay
anchors: { left: 0, right: 0, top: 52, height: 14 }
props:
parameterUid: $param-id
label:
type: text
anchors: { left: 0, right: 0, bottom: 0, height: 14 }
props:
text: $label-text
alignment: bottomCenter

Then instantiate it three times on your main form:

# main.frm — controls section
controls:
knob_cutoff:
type: subForm
pos: { x: 20, y: 40, w: 60, h: 80 }
props:
form: param_knob
macroParameters:
$param-id: "uuid-filter-cutoff"
$label-text: Cutoff
knob_reso:
type: subForm
pos: { x: 90, y: 40, w: 60, h: 80 }
props:
form: param_knob
macroParameters:
$param-id: "uuid-filter-resonance"
$label-text: Reso
knob_drive:
type: subForm
pos: { x: 160, y: 40, w: 60, h: 80 }
props:
form: param_knob
macroParameters:
$param-id: "uuid-filter-drive"
$label-text: Drive

Three fully wired knobs, one form definition, zero repetition.

Three knob instances from one sub-form

Sub-forms can themselves contain subForm controls pointing to further sub-forms. There is no fixed depth limit. A typical production UI might look like:

main.frm
└─ header_bar (subForm)
└─ tab_portal (portal)
├─ osc_tab.frm
│ └─ param_knob × 4 (subForm)
├─ filter_tab.frm
│ └─ param_knob × 3 (subForm)
│ └─ filter_type.frm (subForm)
└─ fx_tab.frm
└─ reverb_panel.frm (subForm)
└─ delay_panel.frm (subForm)

Composition tree diagram

portal is the switching layer — it holds a list of forms and shows one at a time based on a parameter value. It replaces the need for separate tab-panel and conditional-panel concepts.

content:
type: portal
anchors: { left: 0, right: 0, top: 32, bottom: 0 }
props:
parameterUid: tab-index
transition: crossfade
transitionTime: 300
forms:
- form: osc_tab
- form: filter_tab
- form: env_tab

When tab-index is 0, osc_tab is shown; when it is 1, filter_tab, and so on. You typically bind the same parameter to a buttonSelector for the tab header — the two stay in sync automatically.

Each entry in the forms list can have its own macroParameters, just like a subForm:

forms:
- form: channel_strip
macroParameters:
$channel-id: ch1
- form: channel_strip
macroParameters:
$channel-id: ch2

This lets you reuse the same form definition for each portal slot with different data passed in.

transitionDescription
crossfadeFades between forms (default)
slideSlides the outgoing form out

transitionTime is in milliseconds; default is 500.

  • One concern per form. A filter_panel form should know about filter parameters only. Don’t mix concerns.
  • Macros for variation, not duplication. If two forms differ only in the parameter they’re wired to, that’s one form with two subForm instantiations.
  • User-defined parameters for UI state. Tab index, selected mode, expanded/collapsed state — these are UI concerns. Create them as global parameters in Patch Settings.
  • Deep nesting is fine. Three or four levels of sub-form is normal. More than six is a signal to refactor.