Options
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)#
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.
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
@StringPropertyis 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)#
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.
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
useAirStoreoruseAirDatasetmanually for Option Models. - DO NOT pass a
storeRootstring. Pass the entireoptionSetobject from the registry.
Direct Store Listening#
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.
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.






