# Props Reference

A complete reference of all available props for component development. Each prop hook is used via `import { props } from "@props"`.

---

## Table of Contents

1. [Text & Content Props](#text--content-props)
2. [Boolean & Toggle Props](#boolean--toggle-props)
3. [Selection Props](#selection-props)
4. [Numeric Props](#numeric-props)
5. [Typography Props](#typography-props)
6. [Color Props](#color-props)
7. [Media Props](#media-props)
8. [List & Data Props](#list--data-props)
9. [Navigation Props](#navigation-props)
10. [Component System Props](#component-system-props)
11. [onChange Helpers](#onchange-helpers)
12. [Global Module](#global-module)
13. [Primitives Module](#primitives-module)
14. [Variables Module](#variables-module)
15. [CSS Utilities](#css-utilities)
16. [PropEditorLayout Components](#propeditorlayout-components)
17. [Lifecycle Hooks](#lifecycle-hooks)
18. [Common Patterns](#common-patterns)

---

## Text & Content Props

### useText

Text input field for editable string content.

```typescript
const text = props.useText("propName", {
  default: string,              // Required - Default value
  label?: string,               // Label shown in editor
  description?: string,         // Help text below input
  disableable?: boolean,        // Show toggle to disable field
  defaultDisabled?: boolean,    // Start in disabled state
  disabledValue?: string,       // Value when disabled (default: "")
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `string`

**Features:**
- Supports variable interpolation: `"Hello {{userName}}!"` automatically replaces with form values

**Usage Example:**

```typescript
// Editable header with disable toggle
const headerText = props.useText("headerText", {
  label: "Header",
  default: "Welcome aboard!",
  disableable: true,
});

// Simple placeholder text
const placeholderText = props.useText("placeholderText", {
  label: "Placeholder",
  default: "Enter your answer...",
});

// In JSX
{headerText && <h2>{headerText}</h2>}
<input placeholder={placeholderText} />
```

---

### useLink

URL/link input with link styling.

```typescript
const link = props.useLink("propName", {
  default?: string,             // Default URL
  label?: string,               // Label shown in editor
  placeholder?: string,         // Input placeholder
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `string`

**Usage Example:**

```typescript
const externalLink = props.useLink("externalLink", {
  placeholder: "https://example.com",
});

// Use with useFunnelRouter for navigation
const { openExternal } = useFunnelRouter();
<button onClick={() => openExternal(externalLink)}>Visit Link</button>
```

---

## Boolean & Toggle Props

### useBoolean

Toggle switch for boolean values.

```typescript
const isEnabled = props.useBoolean("propName", {
  default: boolean,             // Required - Default value
  label?: string,               // Label shown in editor
  leftIcon?: string,            // Icon name to show on left
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `boolean`

**Usage Example:**

```typescript
// Simple toggle
const isAnswerRequired = props.useBoolean("isAnswerRequired", {
  label: "Answer Required",
  default: true,
});

// Toggle with conditional show/hide of other props
const hasCustomIcon = props.useBoolean("hasCustomIcon", {
  label: "Custom Icon",
  default: false,
  onChange: (value, { hide, show, query }) => {
    if (value) {
      show("icon");
    } else {
      hide("icon");
      query.delete("icon");
    }
  },
});

// Master color toggle pattern
const useCustomColors = props.useBoolean("useCustomColors", {
  label: "Use Custom Colors",
  default: false,
  onChange: (value, { hide, show, query }) => {
    const colorProps = ["headerColor", "subheadColor"];
    if (value) {
      colorProps.forEach((prop) => show(prop));
    } else {
      colorProps.forEach((prop) => {
        hide(prop);
        query.delete(prop);
      });
    }
  },
});
```

---

## Selection Props

### useSelect

Single-select dropdown.

```typescript
const selection = props.useSelect("propName", {
  options: Array<{              // Required - Available options
    label: string,              // Display label
    value: string | null,       // Value when selected
    iconName?: string,          // Optional icon
  }>,
  default: string | null,       // Required - Default value
  label?: string,               // Label shown in editor
  inlineLabel?: string,         // Compact inline label
  inlineLabelIcon?: string,     // Icon for inline label
  leftIcon?: string,            // Icon on left of select
  disableable?: boolean,        // Show toggle to disable
  defaultDisabled?: boolean,    // Start disabled
  disabledValue?: string,       // Value when disabled
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: string) => T,  // Transform function
});
```

**Returns:** `string` or piped type `T`

**Usage Example:**

```typescript
// Simple select with icons
const stickyPosition = props.useSelect("stickyPosition", {
  label: "Position",
  options: [
    { label: "Top", value: "top", iconName: "Position_Top_18PXIcon" },
    { label: "Bottom", value: "bottom", iconName: "Position_Bottom_18PXIcon" },
  ],
  default: "top",
});

// Button width selector
const buttonWidth = props.useSelect("buttonWidth", {
  label: "Button Width",
  options: [
    { label: "Full Width", value: "full" },
    { label: "Auto", value: "auto" },
  ],
  default: "auto",
});

// Dynamic options from another prop
const defaultOption = props.useSelect("defaultOption", {
  label: "Default Option",
  options: options.map(opt => ({ label: opt.label, value: opt.id })),
  default: options[0]?.id || "",
  leftIcon: "Ballot_circle_18PXIcon",
});
```

---

### useMultiSelect

Multi-select dropdown for selecting multiple values.

```typescript
const selections = props.useMultiSelect("propName", {
  options: Array<{              // Required - Available options
    label: string,              // Display label
    value: string,              // Value when selected
    iconName?: string,          // Optional icon
  }>,
  default: string[],            // Required - Default selected values
  label?: string,               // Label shown in editor
  leftIcon?: string,            // Icon on left
  placeholder?: string,         // Placeholder text
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: string[]) => T, // Transform function
});
```

**Returns:** `string[]` or piped type `T`

**Usage Example:**

```typescript
const selectedCategories = props.useMultiSelect("categories", {
  label: "Categories",
  options: [
    { label: "Fitness", value: "fitness" },
    { label: "Nutrition", value: "nutrition" },
    { label: "Wellness", value: "wellness" },
  ],
  default: ["fitness"],
  placeholder: "Select categories...",
});
```

---

### useToggleGroup

Inline toggle button group.

```typescript
const layout = props.useToggleGroup("propName", {
  options: Array<{              // Required - Available options
    label: string,              // Button label
    value: string,              // Value when selected
    iconName?: string,          // Icon for button
  }>,
  default: string,              // Required - Default value
  label?: string,               // Label shown in editor
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: string) => T,  // Transform function
});
```

**Returns:** `string` or piped type `T`

**Usage Example:**

```typescript
// Navigation destination type toggle
const destinationType = props.useToggleGroup("destinationType", {
  label: "Destination Type",
  options: [
    { label: "Funnel Page", value: "funnel", iconName: "Mobile_18PXIcon" },
    { label: "External Link", value: "external", iconName: "Link_18PXIcon" },
  ],
  default: "funnel",
  onChange: (value, { hide, show, query }) => {
    if (value === "funnel") {
      show("destinationPage");
      hide("externalLink");
      query.delete("externalLink");
    } else {
      hide("destinationPage");
      show("externalLink");
      query.delete("destinationPage");
    }
  },
});

// Icon placement toggle
const iconPlacement = props.useToggleGroup("iconPlacement", {
  label: "Icon Placement",
  options: [
    { label: "Vertical", value: "vertical", iconName: "Arrow_Down_18PXIcon" },
    { label: "Horizontal", value: "horizontal", iconName: "Arrow_Right_18PXIcon" },
  ],
  default: "vertical",
});
```

---

### useTextAlignment

Pre-configured toggle group for text alignment.

```typescript
const alignment = props.useTextAlignment("propName", {
  default?: "start" | "center" | "end",  // Default alignment
  label?: string,                         // Label shown in editor
  onChange?: OnChangeFn,                  // Callback when value changes
});
```

**Returns:** `{ textAlign: string }` (CSS object)

**Usage Example:**

```typescript
const textAlignment = props.useTextAlignment("textAlignment", {
  label: "Text Alignment",
  default: "center",
});

// Apply directly with css()
<h2 style={css(headerFontSize, headerColor, textAlignment)}>
  {headerText}
</h2>
```

---

## Numeric Props

### useSlider

Numeric slider with configurable range.

```typescript
const value = props.useSlider("propName", {
  default: number,              // Required - Default value
  min?: number,                 // Minimum value
  max?: number,                 // Maximum value
  step?: number,                // Step increment
  label?: string,               // Label shown in editor
  unit?: string,                // Unit display (e.g., "px", "%")
  subLabel?: string,            // Secondary label/description
  disableable?: boolean,        // Show toggle to disable
  defaultDisabled?: boolean,    // Start disabled
  disabledValue?: number,       // Value when disabled
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: number) => T,  // Transform function
});
```

**Returns:** `number` or piped type `T`

**Usage Example:**

```typescript
// Font size with CSS pipe
const headerFontSize = props.useSlider("headerFontSize", {
  label: "Header",
  default: 20,
  min: 10,
  max: 72,
  unit: "px",
  pipe: css.fontSize,
});

// Character limit (raw number)
const characterLimit = props.useSlider("characterLimit", {
  label: "Character Limit",
  default: 100,
  min: 10,
  max: 500,
  step: 10,
});

// Textarea height with pipe
const textAreaHeight = props.useSlider("textAreaHeight", {
  label: "Height",
  default: 144,
  min: 80,
  max: 300,
  step: 4,
  unit: "px",
  pipe: css.height,
});

// Apply piped slider directly
<h2 style={css(headerFontSize, headerColor)}>{headerText}</h2>
<textarea maxLength={characterLimit} style={css(textAreaHeight)} />
```

---

### useSpacing

Specialized slider for spacing values (0-64px range).

```typescript
const spacing = props.useSpacing("propName", {
  default: number,              // Required - Default value
  label: string,                // Required - Label shown in editor
  subLabel?: string,            // Secondary label (e.g., "(Left & Right)")
  onChange?: OnChangeFn,        // Callback when value changes
  pipe: (value: number) => T,   // Required - Transform function
});
```

**Returns:** Piped type `T` (typically CSS object)

**Common pipes:**
- `css.paddingTop`, `css.paddingBottom`, `css.paddingLeft`, `css.paddingRight`
- `css.paddingHorizontal`, `css.paddingVertical`
- `css.marginTop`, `css.marginBottom`
- `css.gap`

**Usage Example:**

```typescript
// Standard spacing props
const paddingTop = props.useSpacing("paddingTop", {
  label: "Top",
  default: 8,
  pipe: css.paddingTop,
});

const paddingBottom = props.useSpacing("paddingBottom", {
  label: "Bottom",
  default: 8,
  pipe: css.paddingBottom,
});

const paddingSides = props.useSpacing("paddingSides", {
  label: "Side",
  subLabel: "(Left & Right)",
  default: 16,
  pipe: css.paddingHorizontal,
});

const innerSpacing = props.useSpacing("innerSpacing", {
  label: "Text to Subhead",
  default: 4,
  pipe: css.gap,
});

// Apply with css()
<div style={css(paddingTop, paddingBottom, paddingSides)}>
  <div style={css(innerSpacing)}>{/* content */}</div>
</div>
```

---

### useRangeInput

From/To/Step numeric range configuration.

```typescript
const range = props.useRangeInput("propName", {
  default: {                    // Required - Default range
    from: number,
    to: number,
    step: number,
  },
  label?: string,               // Label shown in editor
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `{ from: number; to: number; step: number }`

**Usage Example:**

```typescript
// Range slider configuration
const rangeInput = props.useRangeInput("rangeInput", {
  label: "Range Settings",
  default: { from: 0, to: 500, step: 50 },
});

// Use the values
const minValue = rangeInput.from;  // 0
const maxValue = rangeInput.to;    // 500
const stepValue = rangeInput.step; // 50

// Generate array from range
const items = Array.from(
  { length: Math.floor((maxValue - minValue) / stepValue) + 1 },
  (_, i) => minValue + i * stepValue
);

// Use in slider input
<input
  type="range"
  min={minValue}
  max={maxValue}
  step={stepValue}
/>
```

---

## Typography Props

> **Recommended Pattern:** Use `useFontFamily` with `useFontWeightForFont` (not `useFontWeight`). The `useFontWeightForFont` hook dynamically shows only the font weights available in user-uploaded custom fonts, providing a better UX.

### useFontFamily

Font family selector linked to global fonts.

```typescript
const fontFamily = props.useFontFamily("propName", {
  globalFontKey?: "primaryFont" | "secondaryFont",  // Link to global font
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: string) => T,  // Transform function
});
```

**Returns:** `string` or piped type `T`

**Recommended:** Use `pipe: css.fontFamily`

**Important:** Do NOT add `<Control name="fontFamily" />` in PropEditorLayout. Font family is controlled via global settings, not per-component.

**Usage Example:**

```typescript
// Primary font for headers
const primaryFontFamily = props.useFontFamily("primaryFontFamily", {
  globalFontKey: "primaryFont",
  label: "Primary Font",
  pipe: css.fontFamily,
});

// Secondary font for body text
const secondaryFontFamily = props.useFontFamily("secondaryFontFamily", {
  globalFontKey: "secondaryFont",
  label: "Secondary Font",
  pipe: css.fontFamily,
});

// Apply with other typography styles
<h2 style={css(primaryFontFamily, headerFontWeight, headerFontSize)}>
  {headerText}
</h2>
<p style={css(secondaryFontFamily, subheadFontWeight, subheadFontSize)}>
  {subheadText}
</p>
```

---

### useFontWeight

Standard font weight selector (100-900).

> **Note:** Prefer `useFontWeightForFont` instead - it shows only weights available in user-uploaded fonts.

```typescript
const fontWeight = props.useFontWeight("propName", {
  default: number,              // Required - Default weight (100-900)
  inlineLabel?: string,         // Compact inline label
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `{ fontWeight: number }` (CSS object)

---

### useFontWeightForFont

Dynamic font weight selector based on uploaded font variants.

```typescript
const fontWeight = props.useFontWeightForFont("propName", {
  globalFontKey?: "primaryFont" | "secondaryFont",  // Link to uploaded font
  default: number,              // Default weight
  inlineLabel?: string,         // Compact inline label
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `{ fontWeight: number }` (CSS object)

**Behavior:**
- Shows only weights available in the uploaded font
- Falls back to standard weights (100-900) if no custom font

**Usage Example:**

```typescript
// Link to primary font - shows only available weights
const headerFontWeight = props.useFontWeightForFont("headerFontWeight", {
  globalFontKey: "primaryFont",
  inlineLabel: "Header",
  default: 600,
});

// Link to secondary font
const subheadFontWeight = props.useFontWeightForFont("subheadFontWeight", {
  globalFontKey: "secondaryFont",
  inlineLabel: "Subhead",
  default: 500,
});

// Use with matching font family
<h2 style={css(primaryFontFamily, headerFontWeight, headerFontSize)}>
  {headerText}
</h2>
```

---

### useFontStyleForFont

Dynamic font style selector (normal/italic) based on uploaded font variants.

```typescript
const fontStyle = props.useFontStyleForFont("propName", {
  globalFontKey?: "primaryFont" | "secondaryFont",  // Link to uploaded font
  default?: "normal" | "italic",                    // Default style
  inlineLabel?: string,                             // Compact inline label
  onChange?: OnChangeFn,                            // Callback when value changes
});
```

**Returns:** `"normal" | "italic"` (raw string, not CSS object)

**Usage Example:**

```typescript
// Font style linked to primary font
const headerFontStyle = props.useFontStyleForFont("headerFontStyle", {
  globalFontKey: "primaryFont",
  default: "normal",
});

// Combine font family, weight, and style
<h2 style={css(headerFontFamily, headerFontWeight, { fontStyle: headerFontStyle })}>
  {headerText}
</h2>
```

---

### useFontSize

Font size slider.

```typescript
const fontSize = props.useFontSize("propName", {
  default?: number,             // Default size (default: 16)
  min?: number,                 // Minimum size
  max?: number,                 // Maximum size
  step?: number,                // Step increment
  label?: string,               // Label shown in editor
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: number) => T,  // Transform function
});
```

**Returns:** `{ fontSize: string }` (CSS object) or piped type `T`

**Usage Example:**

```typescript
// Uses default CSS object return
const headerFontSize = props.useFontSize("headerFontSize", {
  label: "Header",
  default: 20,
});

const subheadFontSize = props.useFontSize("subheadFontSize", {
  label: "Subhead",
  default: 16,
});

// Apply directly
<h2 style={css(headerFontSize)}>{headerText}</h2>
```

---

### useBorderRadius

Border radius selector with preset options.

```typescript
const borderRadius = props.useBorderRadius("propName", {
  default?: "none" | "small" | "default" | "large",  // Default preset
  label?: string,               // Label shown in editor
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: string) => T,  // Transform function
});
```

**Returns:** `{ borderRadius: string }` (CSS object) or piped type `T`

**Usage Example:**

```typescript
const cardBorderRadius = props.useBorderRadius("cardBorderRadius", {
  label: "Card Corners",
  default: "default",
});

<div style={css(cardBorderRadius)}>{/* card content */}</div>

// Note: Most components use global.useGlobalData().borderRadius instead
const { borderRadius } = global.useGlobalData();
<div style={{ borderRadius }}>{/* uses global setting */}</div>
```

---

## Color Props

### useColor

Color picker with global color linking.

```typescript
const color = props.useColor("propName", {
  default?: {                   // Optional explicit default
    color: string,              // Hex color
    type: "solid" | "gradient", // Color type
  },
  globalColorKey?: GlobalColorKey,  // Link to global color
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: ColorValue) => T,  // Transform function
});
```

**Returns:** `{ color: string; type: "solid" | "gradient" }` or piped type `T`

**Global Color Keys:**

| Key | Label | Category | Default |
|-----|-------|----------|---------|
| `brandColor` | Brand Color | brand | #f3533f |
| `pageBackground` | Page Background | background | #ffffff |
| `cardBackground` | Card Background | background | #FEFFFF |
| `secondaryCardBackground` | Secondary Card Background | background | #E6E6EF |
| `primaryText` | Primary Text | texts | #0E0E16 |
| `secondaryText` | Secondary Text | texts | #86868a |
| `placeholderText` | Placeholder Text | texts | #86868a |
| `buttonLabel` | Button Label | texts | #FEFFFF |
| `disabledButtonLabel` | Disabled Button Label | texts | rgba(86, 86, 92, 0.7) |
| `primaryButton` | Primary Button | buttons | #F2523F |
| `disabledButton` | Disabled Button | buttons | #EEEEF1 |
| `activeInputFieldBG` | Active Input Field | input fields | #F2523F |
| `inactiveInputFieldBG` | Input Field | input fields | #ECEDF3 |
| `activeUserInput` | Active User Input | multiple choice | #F2523F |
| `inactiveUserInput` | Inactive User Input | multiple choice | #FEFFFF |
| `primaryIcon` | Primary Icon | icons | #86868A |
| `secondaryIcon` | Secondary Icon | icons | #FEFFFF |
| `primaryStroke` | Primary Stroke | stroke | #E4E5EB |

**Common pipes:**
- `css.color` - For text colors
- `css.background` - For background colors

**Usage Example:**

```typescript
// Text color with pipe
const headerColor = props.useColor("headerColor", {
  label: "Header",
  globalColorKey: "primaryText",
  pipe: css.color,
});

// Background color with pipe
const cardBackgroundColor = props.useColor("cardBackgroundColor", {
  label: "Card Background",
  globalColorKey: "cardBackground",
  pipe: css.background,
});

// Color without pipe (for manual use)
const highlightColor = props.useColor("highlightColor", {
  label: "Highlight",
  globalColorKey: "brandColor",
});

// Using piped colors
<h2 style={css(headerColor)}>{headerText}</h2>
<div style={css(cardBackgroundColor)}>{/* content */}</div>

// Using raw color value
<div style={{ borderColor: highlightColor.color }}>
  {/* border uses highlight color */}
</div>

// Common pattern: Master color toggle
const useCustomColors = props.useBoolean("useCustomColors", {
  label: "Use Custom Colors",
  default: false,
  onChange: (value, { hide, show, query }) => {
    const colorProps = ["headerColor", "subheadColor", "iconColor"];
    if (value) {
      colorProps.forEach((prop) => show(prop));
    } else {
      colorProps.forEach((prop) => {
        hide(prop);
        query.delete(prop);
      });
    }
  },
});
```

---

## Media Props

### useIcon

Single icon picker (emoji, icon library, or custom upload).

```typescript
const icon = props.useIcon("propName", {
  default?: {                   // Default icon
    type: "emoji" | "icon" | "custom",
    name: string,               // Emoji char, icon name, or URL
  },
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `{ type: "emoji" | "icon" | "custom"; name: string } | undefined`

**Rendering:**
```typescript
{icon && (
  <>
    {icon.type === "emoji" && <span>{icon.name}</span>}
    {icon.type === "icon" && <DynamicIcon name={icon.name} />}
    {icon.type === "custom" && <img src={icon.name} alt="" />}
  </>
)}
```

**Usage Example:**

```typescript
// Conditional icon with toggle
const hasCustomIcon = props.useBoolean("hasCustomIcon", {
  label: "Custom Icon",
  default: false,
  onChange: (value, { hide, show, query }) => {
    if (value) {
      show("icon");
    } else {
      hide("icon");
      query.delete("icon");
    }
  },
});

const icon = props.useIcon("icon", {
  label: "Icon",
  default: { type: "icon", name: "Bookmark_18PXIcon" },
});

// Render with color styling
{hasCustomIcon && icon && (
  <div style={iconColor ? css(iconColor) : undefined}>
    {icon.type === "emoji" && <span className="text-xl">{icon.name}</span>}
    {icon.type === "icon" && <DynamicIcon name={icon.name} className="size-4" />}
    {icon.type === "custom" && (
      <img src={icon.name} alt="" className="size-4 rounded object-cover" />
    )}
  </div>
)}
```

---

### usePicture

Image upload field.

```typescript
const imageUrl = props.usePicture("propName", {
  default?: string,             // Default image URL
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `string` (image URL)

**Usage Example:**

```typescript
const customerPicture = props.usePicture("customerPicture", {
  label: "Customer Picture",
  description: "Upload or drop a photo for the customer profile.",
  default: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=256&h=256",
});

// Render with fallback
{customerPicture && (
  <img
    src={customerPicture}
    alt="Customer"
    className="w-12 h-12 rounded-full object-cover"
  />
)}
```

---

### useVideo

Video upload field.

```typescript
const videoUrl = props.useVideo("propName", {
  default?: string,             // Default video URL
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `string` (video URL)

**Usage Example:**

```typescript
const backgroundVideo = props.useVideo("backgroundVideo", {
  label: "Background Video",
  description: "Upload a video for the background.",
});

{backgroundVideo && (
  <video
    src={backgroundVideo}
    autoPlay
    loop
    muted
    playsInline
    className="absolute inset-0 w-full h-full object-cover"
  />
)}
```

---

## List & Data Props

### useOptions

Dynamic list editor for arrays of complex objects.

```typescript
const options = props.useOptions("propName", {
  default: Array<OptionItem>,   // Required - Default items
  schema: {                     // Required - Field configuration
    label?: boolean | Record<string, any>,
    description?: boolean | Record<string, any>,
    url?: boolean | Record<string, any>,
    icon?: boolean | Record<string, any>,
    image?: boolean | Record<string, any>,
    index?: boolean | Record<string, any>,
    ordinal?: boolean | Record<string, any>,
  },
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  itemLabel?: string,           // Label for each item (e.g., "Answer")
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `OptionItem[]`

```typescript
interface OptionItem {
  id: string;                   // Unique identifier
  label?: string;               // Text label
  description?: string;         // Description text
  url?: string;                 // URL field
  icon?: {                      // Icon object
    type: "emoji" | "icon" | "custom";
    name: string;
  };
  image?: string;               // Image URL
}
```

**Conditional schema fields:**
```typescript
const hasIcons = props.useBoolean("hasIcons", { default: false });

const options = props.useOptions("options", {
  schema: {
    label: true,
    icon: hasIcons,  // Only show when hasIcons is true
  },
  default: [],
});
```

**Usage Example:**

```typescript
// FAQ with questions and answers
const questionsAndAnswers = props.useOptions("questionsAndAnswers", {
  label: "Questions & Answers",
  schema: {
    label: true,        // Question text
    description: true,  // Answer text
  },
  default: [
    { id: "1", label: "Question 1", description: "Answer 1" },
    { id: "2", label: "Question 2", description: "Answer 2" },
  ],
  itemLabel: "Q&A",
});

// Feature list with conditional icons
const hasIcons = props.useBoolean("hasIcons", {
  label: "Enable Icons",
  default: false,
  onChange: (value, { query, get, set }) => {
    if (value) {
      // Add default icons to existing items
      const currentOptions = get("options") || [];
      const updated = currentOptions.map((opt) => ({
        ...opt,
        icon: opt.icon || { type: "icon", name: "Check_18PXIcon" },
      }));
      set("options", updated);
    } else {
      query.delete("options.*.icon");
    }
  },
});

const options = props.useOptions("options", {
  label: "Features",
  schema: {
    label: true,
    icon: hasIcons,
  },
  default: [
    { id: "1", label: "Feature 1" },
    { id: "2", label: "Feature 2" },
  ],
  itemLabel: "Feature",
});

// Render options with icons
{options.map((option) => (
  <div key={option.id} className="flex items-center gap-2">
    {option.icon && (
      <DynamicIcon name={option.icon.name} className="size-5" />
    )}
    <span>{option.label}</span>
  </div>
))}
```

---

### usePlans

Subscription plan editor with pricing tiers.

```typescript
const plans = props.usePlans("propName", {
  default: PlanItem[],          // Required - Default plans
  schema: {                     // Required - Field configuration
    label?: boolean | Record<string, any>,
    url?: boolean | Record<string, any>,
    description?: boolean | Record<string, any>,
    tierOptions?: Array<{ label: string; value: string }>,
  },
  label?: string,               // Label shown in editor
  description?: string,         // Help text
  itemLabel?: string,           // Label for each plan
  tierOptions?: Array<{ label: string; value: string }>,
  onChange?: OnChangeFn,        // Callback when value changes
});
```

**Returns:** `PlanItem[]`

```typescript
interface PlanItem {
  id: string;
  pricingTierId?: string;
  label?: string;
  url?: string;
  description?: string;
  hasDiscountBadge?: boolean;
  hasPromotionalBadge?: boolean;
  hasAlternativePricing?: boolean;
  hasNonDiscountedPrice?: boolean;
  discountBadge?: string;
  promotionalBadgeStyle?: "strong" | "subtle";
  alternativePricingPrice?: string;
  alternativePricingDuration?: string;
  nonDiscountedPrice?: string;
}
```

**Usage Example:**

```typescript
const plans = props.usePlans("plans", {
  label: "SUBSCRIPTION PLANS",
  schema: {
    label: true,
    url: true,
    description: true,
  },
  default: [],
  itemLabel: "Plan",
});

// Render plan cards
{plans.map((plan) => (
  <div key={plan.id} className="border rounded-lg p-4">
    <h3>{plan.label}</h3>
    {plan.description && <p>{plan.description}</p>}
    {plan.hasDiscountBadge && (
      <span className="badge">{plan.discountBadge}</span>
    )}
  </div>
))}
```

---

## Navigation Props

### usePageSelect

Funnel page selector for internal navigation.

```typescript
const destination = props.usePageSelect("propName", {
  default?: string | null,      // Default page ID
  label?: string,               // Label shown in editor
  excludeCurrentPage?: boolean, // Hide current page from options
});
```

**Returns:**
```typescript
{
  pageId: string | null;        // Selected page ID
  page: {                       // Full page object (or null)
    id: string;
    title: string;
    slug: string;
  } | null;
  slug: string | null;          // Page slug for navigation
}
```

**Usage with navigation:**
```typescript
import { useFunnelRouter } from "@primitives";

const { push } = useFunnelRouter();
const destination = props.usePageSelect("destinationPage", {
  excludeCurrentPage: true,
});

const handleClick = () => {
  if (destination.slug) {
    push(destination.slug);
  }
};
```

---

  ## Component System Props

### useVariants

Component variant selector.

```typescript
const variant = props.useVariants({
  default: string,              // Required - Default variant
  onChange?: OnChangeFn,        // Callback when variant changes
});
```

**Returns:** `string` (current variant value)

**Defining variants:**
```typescript
MyComponent.variants = [
  {
    label: "Default",           // Display name
    props: {                    // Props to apply
      _variant: "default",
    },
    variables: {},              // Initial form variables
  },
  {
    label: "Large",
    props: {
      _variant: "large",
      headerFontSize: 32,       // Override any prop
      useCustomColors: true,    // Required for color overrides
      headerColor: { color: "#f3533f", type: "solid" },
    },
    variables: {},
  },
];
```

**Usage Example:**

```typescript
export default function MultipleChoice() {
  const variant = props.useVariants({
    default: "vertical-stack",
    onChange: (value, { get, show, hide, query }) => {
      if (value === "vertical-stack") {
        hide("hasOptionImages");
        hide("fitContent");
        query.delete("hasOptionImages");
        query.delete("fitContent");
        // Conditionally show checkbox for vertical
        const hasMultiple = get("isMultipleSelection");
        if (hasMultiple) {
          show("showCheckbox");
        }
      } else if (value === "grid-stack") {
        show("hasOptionImages");
        show("fitContent");
        hide("showCheckbox");
        query.delete("showCheckbox");
      }
    },
  });

  // Render based on variant
  switch (variant) {
    case "vertical-stack":
      return <MultipleChoiceVertical />;
    case "grid-stack":
      return <MultipleChoiceGrid />;
    default:
      return <MultipleChoiceVertical />;
  }
}

// Define available variants
MultipleChoice.variants = [
  {
    label: "Vertical Stack",
    props: { _variant: "vertical-stack" },
    variables: { selectedAnswers: ["1"] },
  },
  {
    label: "Grid Stack",
    props: { _variant: "grid-stack" },
    variables: { selectedAnswers: ["1"] },
  },
];
```

---

### useObjectSelect

Generic single object selector.

```typescript
const selected = props.useObjectSelect("propName", {
  items: T[],                   // Required - Available items
  default: T | null,            // Required - Default selection
  getLabel: (item: T) => string | React.ReactNode,  // Label renderer
  label?: string,               // Label shown in editor
  leftIcon?: string,            // Icon on left
  disableable?: boolean,        // Show toggle to disable
  defaultDisabled?: boolean,    // Start disabled
  disabledValue?: T | null,     // Value when disabled
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: T | null) => R,  // Transform function
});
// Note: T must extend { key: string; iconName?: string }
```

**Returns:** `T | null` or piped type `R`

**Usage Example:**

```typescript
interface ThemeOption {
  key: string;
  iconName: string;
  label: string;
  colors: { primary: string; secondary: string };
}

const themes: ThemeOption[] = [
  { key: "light", iconName: "Sun_18PXIcon", label: "Light", colors: { primary: "#fff", secondary: "#000" } },
  { key: "dark", iconName: "Moon_18PXIcon", label: "Dark", colors: { primary: "#000", secondary: "#fff" } },
];

const selectedTheme = props.useObjectSelect("theme", {
  items: themes,
  default: themes[0],
  getLabel: (theme) => theme.label,
  label: "Theme",
});

// Use selected theme
<div style={{ background: selectedTheme?.colors.primary }}>
  {/* content */}
</div>
```

---

### useObjectMultiSelect

Generic multi-object selector.

```typescript
const selected = props.useObjectMultiSelect("propName", {
  items: T[],                   // Required - Available items
  default: T[],                 // Required - Default selections
  getLabel: (item: T) => string | React.ReactNode,  // Label renderer
  label?: string,               // Label shown in editor
  leftIcon?: string,            // Icon on left
  placeholder?: string,         // Placeholder text
  onChange?: OnChangeFn,        // Callback when value changes
  pipe?: (value: T[]) => R,     // Transform function
});
// Note: T must extend { key: string; iconName?: string }
```

**Returns:** `T[]` or piped type `R`

**Usage Example:**

```typescript
interface Feature {
  key: string;
  iconName: string;
  name: string;
}

const features: Feature[] = [
  { key: "analytics", iconName: "Chart_18PXIcon", name: "Analytics" },
  { key: "export", iconName: "Download_18PXIcon", name: "Export" },
  { key: "api", iconName: "Code_18PXIcon", name: "API Access" },
];

const selectedFeatures = props.useObjectMultiSelect("features", {
  items: features,
  default: [features[0]],
  getLabel: (f) => f.name,
  label: "Features",
  placeholder: "Select features...",
});

// Render selected features
{selectedFeatures.map((feature) => (
  <DynamicIcon key={feature.key} name={feature.iconName} />
))}
```

---

## onChange Helpers

The `onChange` callback receives helpers for dynamic prop control:

```typescript
onChange: (value, helpers) => {
  const {
    get,      // Get another prop's value
    set,      // Set another prop's value
    hide,     // Hide a prop in editor
    show,     // Show a prop in editor
    enable,   // Enable a prop
    disable,  // Disable a prop
    query,    // Query operations
  } = helpers;
}
```

### get(propName: string): any

Get the current value of another prop.

```typescript
const otherValue = get("otherProp");
```

### set(propName: string, value: any): void

Set the value of another prop.

```typescript
set("otherProp", "new value");
```

### hide(propName: string): void

Hide a prop from the editor UI.

```typescript
hide("conditionalProp");
```

### show(propName: string): void

Show a hidden prop in the editor UI.

```typescript
show("conditionalProp");
```

### enable(propName: string): void

Enable a disabled prop.

### disable(propName: string): void

Disable a prop.

### query.set(path: string, value: any): void

Set nested data using dot notation with wildcards.

```typescript
query.set("answers.*.icon", { type: "icon", name: "Star_18PXIcon" });
```

### query.delete(path: string): void

Delete nested data using dot notation with wildcards.

```typescript
query.delete("answers.*.icon");  // Remove all icons from answers
query.delete("customColor");     // Remove a single prop
```

**Comprehensive Usage Example:**

```typescript
// Master toggle that controls multiple dependent props
const useCustomColors = props.useBoolean("useCustomColors", {
  label: "Use Custom Colors",
  default: false,
  onChange: (value, { hide, show, query }) => {
    const colorProps = ["headerColor", "subheadColor", "iconColor"];
    if (value) {
      colorProps.forEach((prop) => show(prop));
    } else {
      colorProps.forEach((prop) => {
        hide(prop);
        query.delete(prop);  // Clean up values when hiding
      });
    }
  },
});

// Toggle that adds default data to list items
const hasIcons = props.useBoolean("hasIcons", {
  label: "Enable Icons",
  default: false,
  onChange: (value, { query, get, set }) => {
    if (value) {
      // Add default icons to all existing options
      const currentOptions = get("options") || [];
      const updated = currentOptions.map((opt) => ({
        ...opt,
        icon: opt.icon || { type: "icon", name: "Check_18PXIcon" },
      }));
      set("options", updated);
    } else {
      // Remove all icons using wildcard
      query.delete("options.*.icon");
    }
  },
});

// Variant-based prop visibility
const variant = props.useVariants({
  default: "simple",
  onChange: (value, { get, show, hide, query }) => {
    if (value === "advanced") {
      show("advancedSettings");
      show("customSpacing");
    } else {
      hide("advancedSettings");
      hide("customSpacing");
      query.delete("advancedSettings");
      query.delete("customSpacing");
    }
  },
});
```

---

## Global Module

Import: `import { global } from "@global"`

### global.useGlobalData()

Access global funnel configuration.

```typescript
const {
  buttonStyle,                  // "detailed" | "flat"
  navigationButtonTopSpacing,   // number
  navigationButtonBottomSpacing,// number
  navigationButtonSideSpacing,  // number
  multipleChoiceAutoNavDelay,   // number (ms)
  borderRadius,                 // string
  continueButtonTextSize,       // number
  continueButtonFontWeight,     // number
  continueButtonBorderRadius,   // number
} = global.useGlobalData();
```

### global.useLogo()

Get the funnel logo URL.

```typescript
const logoUrl = global.useLogo();  // string | undefined
```

### global.usePageProgress()

Get current page progress.

```typescript
const {
  currentPageIndex,  // number
  totalPages,        // number
} = global.usePageProgress();
```

### global.useControlContinueButton()

Control the Continue button visibility.

```typescript
const { show, hide } = global.useControlContinueButton();

show("Custom Label");  // Show with custom label
show();                // Show with default label
hide();                // Hide the button
```

### global.hideContinueButton(blockId, store)

Hide Continue button from `onAddedToPage` lifecycle hook.

```typescript
MyComponent.onAddedToPage = ({ blockId, store, hideContinueButton }) => {
  hideContinueButton(blockId, store);
};
```

### global.showContinueButton(blockId, store, label?)

Show Continue button from `onAddedToPage` lifecycle hook.

**Usage Example:**

```typescript
import { global } from "@global";

function MultipleChoice() {
  // Access global configuration
  const { 
    multipleChoiceAutoNavDelay,  // Auto-nav delay (ms)
    borderRadius,                 // Global border radius
    buttonStyle,                  // "detailed" | "flat"
  } = global.useGlobalData();

  // Control continue button visibility
  const { show: showContinueButton, hide: hideContinueButton } =
    global.useControlContinueButton();

  // Show progress
  const { currentPageIndex, totalPages } = global.usePageProgress();
  const progress = ((currentPageIndex + 1) / totalPages) * 100;

  // Handle selection with auto-nav
  const handleSelect = async (optionId: string) => {
    setValue("selectedAnswer", optionId);
    
    // Auto-navigate after delay
    if (multipleChoiceAutoNavDelay > 0) {
      hideContinueButton();
      await new Promise(r => setTimeout(r, multipleChoiceAutoNavDelay));
      await funnelRouter.next();
    } else {
      showContinueButton();
    }
  };

  return (
    <div style={{ borderRadius }}>
      {/* Progress bar */}
      <div style={{ width: `${progress}%` }} />
      {/* Component content */}
    </div>
  );
}

// Lifecycle: Hide continue button when component is added
MultipleChoice.onAddedToPage = ({ blockId, store, hideContinueButton }) => {
  hideContinueButton(blockId, store);
};
```

---

## Primitives Module

Import: `import { DynamicIcon, useFunnelRouter, s } from "@primitives"`

### DynamicIcon

Dynamic icon component that loads icons by name.

```typescript
<DynamicIcon
  name="Star_18PXIcon"          // Required - Icon name
  className="size-5"            // Optional - CSS classes
  fallback={<span>...</span>}   // Optional - Loading fallback
  errorFallback={<span>?</span>}// Optional - Error fallback
  // ... other SVG props
/>
```

**Icon naming:** Convert filename to component name:
- `18px_star.svg` → `Star_18PXIcon`
- `24px_arrow_right.svg` → `Arrow_Right_24PXIcon`

### useFunnelRouter()

Hook for programmatic navigation.

```typescript
const {
  slug,           // Current page slug
  pageId,         // Current page ID
  push,           // Navigate to page by slug or ID
  back,           // Go back in history
  openExternal,   // Open external URL in new tab
  next,           // Execute navigation guards and go to next page
  useNavigationGuard,  // Register a navigation guard
} = useFunnelRouter();

// Navigate to a page
push("page-slug");

// Open external link
openExternal("https://example.com");

// Go to next page (respects routing rules)
await next();

// Register a guard that can block navigation
useNavigationGuard(() => {
  if (!isValid) return false;  // Block navigation
  return true;                  // Allow navigation
}, [isValid]);
```

### s (Stylable Elements)

Wrapper components for HTML elements with styling support.

```typescript
<s.div name="container" className="flex">
  <s.h1 name="title">Hello</s.h1>
  <s.p name="description">World</s.p>
</s.div>
```

Available elements: `div`, `span`, `img`, `p`, `h1`-`h6`, `strong`, `em`, `a`, `button`, `ul`, `ol`, `li`, `header`, `footer`, `section`, `article`, `input`, `textarea`, `select`, `label`, `table`, `tr`, `td`, `th`, and more.

**Usage Example:**

```typescript
import { DynamicIcon, useFunnelRouter } from "@primitives";

function CustomButton() {
  const { push, openExternal } = useFunnelRouter();
  
  // Navigation props
  const destinationType = props.useToggleGroup("destinationType", {
    options: [
      { label: "Funnel Page", value: "funnel" },
      { label: "External Link", value: "external" },
    ],
    default: "funnel",
  });
  
  const destination = props.usePageSelect("destinationPage", {
    excludeCurrentPage: true,
  });
  
  const externalLink = props.useLink("externalLink", {
    placeholder: "https://example.com",
  });
  
  const icon = props.useIcon("icon", {
    default: { type: "icon", name: "Arrow_Right_18PXIcon" },
  });

  const handleClick = () => {
    if (destinationType === "funnel" && destination.slug) {
      push(destination.slug);  // Navigate to funnel page
    } else if (externalLink) {
      openExternal(externalLink);  // Open external URL
    }
  };

  return (
    <button onClick={handleClick} className="flex items-center gap-2">
      <span>Get Started</span>
      {icon && icon.type === "icon" && (
        <DynamicIcon name={icon.name} className="size-4" />
      )}
    </button>
  );
}

// Using navigation guard for form validation
function InputField() {
  const { useNavigationGuard } = useFunnelRouter();
  const { getValues } = useFormContext();
  
  // Block navigation if email is invalid
  useNavigationGuard(() => {
    const email = getValues("email");
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    return isValid;  // false blocks navigation
  }, []);

  return <input name="email" />;
}
```

### Auto-Navigation Pattern

Auto-navigate to next page after a timed process (loading screens, step loaders).

```typescript
const funnelRouter = useFunnelRouter();
const [isComplete, setIsComplete] = useState(false);

const loadingDuration = props.useSlider("loadingDuration", {
  label: "Duration", default: 10000, min: 1000, max: 30000, unit: "ms",
});

// Trigger completion after duration
useEffect(() => {
  const timer = setTimeout(() => setIsComplete(true), loadingDuration);
  return () => clearTimeout(timer);
}, [loadingDuration]);

// Auto-navigate when complete
useEffect(() => {
  if (isComplete) {
    const timer = setTimeout(() => funnelRouter.next(), 500);
    return () => clearTimeout(timer);
  }
}, [isComplete, funnelRouter]);

// Hide continue button - user shouldn't manually navigate
MyComponent.onAddedToPage = ({ blockId, store, hideContinueButton }) => {
  hideContinueButton(blockId, store);
};
```

---

## Variables Module

Import: `import { useDeclareVariable, useVariable } from "@variables"`

### useDeclareVariable

Declare a new variable for the funnel.

```typescript
const [value, setValue] = useDeclareVariable(
  name: string,                 // Variable name
  type: "string" | "number" | "boolean" | "object" | "date",
  defaultValue: T,              // Initial value
  metadata?: Record<string, any>  // Optional metadata
);
```

### useVariable

Access an existing variable.

```typescript
const [value, setValue] = useVariable(
  name: string,                 // Variable name
  defaultValue?: T              // Fallback value
);
```

**Usage Example (Input Components):**

Input components use `fieldId` prop + `Controller` from react-hook-form. The `fieldId` becomes the variable name that stores user input.

```typescript
import { Controller, useFormContext } from "react-hook-form";

function InputField() {
  const { control } = useFormContext();
  
  // fieldId prop - becomes the variable name
  const fieldName = props.useText("fieldId", {
    label: "Component ID",
    default: "inputField",
  });

  return (
    <Controller
      name={fieldName}           // Variable name from prop
      control={control}
      defaultValue=""
      variableType="string"      // "string" | "number" | "object" | "boolean"
      render={({ field }) => (
        <input
          value={field.value}
          onChange={(e) => field.onChange(e.target.value)}
        />
      )}
    />
  );
}

// Generate unique variable name when component is added
InputField.onAddedToPage = ({ blockId, store }) => {
  store.setBlockProp(
    blockId,
    "fieldId",
    `inputField_${crypto.randomUUID().slice(0, 8)}`
  );
};
```

---

## CSS Utilities

Import: `import { css } from "@/lib/utils"`

### css()

Merge multiple style objects.

```typescript
<div style={css(paddingTop, backgroundColor, textAlignment)}>
```

### Typography

```typescript
css.fontWeight(600)      // { fontWeight: 600 }
css.fontSize(16)         // { fontSize: "16px" }
css.textAlign("center")  // { textAlign: "center" }
css.fontFamily("...")    // { fontFamily: "..." }
css.fontStyle("italic")  // { fontStyle: "italic" }
```

### Colors

```typescript
// Solid color
css.color({ color: "#ff0000", type: "solid" })
// Returns: { color: "#ff0000" }

// Gradient color (for text)
css.color({ color: "linear-gradient(...)", type: "gradient" })
// Returns: { backgroundImage, WebkitBackgroundClip, backgroundClip, color: "transparent" }

// Background
css.background({ color: "#ff0000", type: "solid" })
// Returns: { background: "#ff0000" }
```

### Spacing

```typescript
css.paddingTop(16)       // { paddingTop: "16px" }
css.paddingBottom(16)    // { paddingBottom: "16px" }
css.paddingLeft(16)      // { paddingLeft: "16px" }
css.paddingRight(16)     // { paddingRight: "16px" }
css.paddingHorizontal(16)// { paddingLeft: "16px", paddingRight: "16px" }
css.paddingVertical(16)  // { paddingTop: "16px", paddingBottom: "16px" }

css.marginTop(16)        // { marginTop: "16px" }
css.marginBottom(16)     // { marginBottom: "16px" }
// ... same pattern for margin
```

### Layout

```typescript
css.height(100)          // { height: "100px" }
css.gap(8)               // { gap: "8px" }
css.borderRadius(8)      // { borderRadius: "8px" }
```

**Usage Example:**

```typescript
// Props with pipes return CSS objects directly
const paddingTop = props.useSpacing("paddingTop", { default: 8, pipe: css.paddingTop });
const headerFontSize = props.useSlider("headerFontSize", { default: 20, pipe: css.fontSize });
const headerColor = props.useColor("headerColor", { globalColorKey: "primaryText", pipe: css.color });

// Merge all CSS objects with css()
<h2 style={css(paddingTop, headerFontSize, headerColor, textAlignment)}>
  {headerText}
</h2>

// Manual css helpers for computed values
<div style={css(css.fontWeight(600), css.fontSize(14), { lineHeight: "1.4" })}>
  {subheadText}
</div>
```

---

## PropEditorLayout Components

When defining `MyComponent.PropEditorLayout`:

```typescript
MyComponent.PropEditorLayout = ({ 
  Section,      // Main container
  Separator,    // Section header with optional label
  Control,      // Renders prop editor by name
  Label,        // Sub-section label
  VisibleWhen,  // Conditional visibility wrapper
}) => {
  return (
    <Section className="flex flex-col p-4">
      <Separator label="SECTION" />
      <Control name="propName" />
      
      <VisibleWhen anyOf={["prop1", "prop2"]}>
        <Label>Sub-section</Label>
        <Control name="prop1" />
        <Control name="prop2" />
      </VisibleWhen>
    </Section>
  );
};
```

### Section

Main container for the props panel.

### Separator

Section divider with optional label.

```typescript
<Separator label="CONFIGURATION" />
<Separator />  // Just a line
```

### Control

Renders the appropriate editor for a prop.

```typescript
<Control name="headerText" />
```

### Label

Sub-section label (smaller than Separator).

```typescript
<Label>Font Color</Label>
```

### VisibleWhen

Conditionally shows children when ANY of the specified props are visible.

```typescript
<VisibleWhen anyOf={["headerColor", "subheadColor"]}>
  <Label>Colors</Label>
  <Control name="headerColor" />
  <Control name="subheadColor" />
</VisibleWhen>
```

---

## Lifecycle Hooks

### onAddedToPage

Runs once when a component is added to the canvas.

```typescript
MyComponent.onAddedToPage = ({ 
  blockId,              // Unique block ID
  store,                // Funnel schema store
  hideContinueButton,   // Function to hide continue button
  showContinueButton,   // Function to show continue button
}) => {
  // Initialize unique variable names
  store.setBlockProp(
    blockId,
    "fieldId",
    `field_${crypto.randomUUID().slice(0, 8)}`
  );
  
  // Hide continue button for auto-navigating components
  hideContinueButton(blockId, store);
};
```

---

## Common Patterns

### Custom Positioning (Sticky)

Allow components to be positioned sticky at top or bottom of the viewport.

```typescript
// Toggle to enable custom positioning
const customPositioning = props.useBoolean("customPositioning", {
  label: "Custom Positioning",
  default: false,
  onChange: (value, { hide, show, query, set, get }) => {
    if (value) {
      show("stickyPosition");
      const currentPosition = get("stickyPosition");
      if (!currentPosition) {
        set("stickyPosition", "top");
      }
    } else {
      hide("stickyPosition");
      query.delete("stickyPosition");
    }
  },
});

const stickyPosition = props.useSelect("stickyPosition", {
  label: "Position",
  options: [
    { label: "Top", value: "top", iconName: "Position_Top_18PXIcon" },
    { label: "Bottom", value: "bottom", iconName: "Position_Bottom_18PXIcon" },
  ],
  default: "top",
});

// IMPORTANT: Prevent unused var removal (props still need to register)
void customPositioning;
void stickyPosition;
```

In PropEditorLayout:

```typescript
<Control name="customPositioning" />
<VisibleWhen anyOf={["stickyPosition"]}>
  <Control name="stickyPosition" />
</VisibleWhen>
```
