Options

Architectural system synchronizing Cloud Datasets, React UI selection hooks, and 3D Engine Operators via the Twin-Proxy Vault.
Published: 7/10/2026

The Options system is the core data-binding pipeline in Aircada product configurators. It binds predefined catalog choices (such as colors, materials, or dimensions) from cloud spreadsheets directly to React hooks and Engine Operator properties without requiring manual event handling.

Engine Operator Guidelines (@DatasetMapping)#

Receiving UI selections in Engine Operators through declarative @DatasetMapping bindings.

Link a Trigger Property directly to an optionSet. The system automatically fetches the row and maps columns to Operator properties.

The @Property decorator on the trigger is optional. Include it only if you want to expose the raw Row ID string in the Aircada Visual Studio UI.

typescript
import { DatasetMapping, Operator, FloatProperty, StringProperty } from "@aircada/spec";
import { OptionsRegistry } from "../registry/options.generated";

@Operator({ name: "Material Controller", type: "material_controller" })
export class MaterialController {
    @StringProperty
    carColor: string = "#ffffff";

    @DatasetMapping({
        optionSet: OptionsRegistry.BALL_O_STEEL.sets.SIMPLECOLORS,
        mapping: { carColor: "color" } 
    })
    @StringProperty
    activeColorId: string = "";
}
  • The trigger @StringProperty is only required if the Row ID needs to be visible/editable in the Studio UI.
  • DO NOT write manual Event Bus listeners for Option Models.
  • DO NOT fetch dataset rows manually inside your Operator.
  • DO NOT write mapping logic. Let the decorator handle data injection.

React UI Guidelines (useAirOptions)#

Using the useAirOptions hook for all product configurator UI components.

The hook acts as a pure, data-bound abstraction that handles fetching dataset rows, reading Vault state, and safely patching using deep-string paths.

typescript
import { useAirOptions } from '@aircada/air-react';
import { OptionsRegistry } from '../registry/options.generated';

export function ColorPicker() {
    const { 
        options, 
        isLoading, 
        activeSelection, 
        selectOption 
    } = useAirOptions(OptionsRegistry.BALL_O_STEEL.sets.SIMPLECOLORS);

    if (isLoading) return <div>Loading...</div>;

    return (
        <div className="flex gap-2">
            {options.map(row => (
                <button
                    key={row.id}
                    className={activeSelection?.id === row.id ? 'border-cyan-500' : 'border-gray-500'}
                    onClick={() => selectOption(row)}
                >
                    {row.name}
                </button>
            ))}
        </div>
    );
}
  • DO NOT use useAirStore or useAirDataset manually for Option Models.
  • DO NOT pass a storeRoot string. Pass the entire optionSet object from the registry.

Direct Store Listening#

Listen to option set selections directly in Engine Operators using store path decorators.

Instead of using @DatasetMapping, the preferred modern pattern for Operators working with options is to listen directly to the store path of the option set using the @Input decorator's onStore property.

This pattern is especially preferred when you need to target scene objects and apply imperative updates—such as changing 3D materials, transforms, or components—directly when the selection changes.

typescript
import { Input, Operator } from "@aircada/spec";
import { OptionsRegistry, BottleBodyFinishRow } from "../registry/options.generated";

@Operator({ name: "Bottle Operator", type: "bottle_operator" })
export class BottleOperator {
    // Reference to the bottle mesh facade
    bottleModel: any;

    @Input({ onStore: OptionsRegistry.HYDROSHIFT_BOTTLE_CUSTOMIZER.sets.BODYFINISH.storePath })
    onBodyFinishChange(val: BottleBodyFinishRow) {
        if (!val) return;
        console.log("[BottleOperator] Body finish change:", val.name);
        this.bottleModel.material.roughness = val.roughness ?? 0.8;
        this.bottleModel.material.metalness = val.metalness ?? 0.1;
    }
}
  • Use @Input({ onStore: OptionsRegistry.[CONFIG].sets.[SET].storePath }) to listen to selection changes.
  • The callback receives the fully typed row object representing the active option selection.
  • This pattern is preferred for direct, imperative mutations of 3D scene objects over @DatasetMapping.