| title | Widget Contract |
|---|---|
| description | Standard props, events, and lifecycle for ObjectUI components |
import { Component, Zap, Code, Check, AlertCircle, Globe } from 'lucide-react';
The Widget Contract defines the standard interface that all ObjectUI components must implement. This contract ensures consistency across renderers and enables components to be composed, configured, and extended predictably.
ObjectUI follows React's unidirectional data flow pattern:
┌─────────────────────────────────────┐
│ Parent Component │
│ (Form, Section, Container) │
└─────────────┬───────────────────────┘
│
│ Props ↓ (data, config, callbacks)
│
┌─────────────▼───────────────────────┐
│ Child Component │
│ (TextField, Select, Button) │
└─────────────┬───────────────────────┘
│
│ Events ↑ (onChange, onClick, onBlur)
│
┌─────────────▼───────────────────────┐
│ Parent Component │
│ (Updates state, triggers actions) │
└─────────────────────────────────────┘
Key Principles:
- Props are immutable: Components receive props, never modify them
- Events are declarative: Components emit events, parent handles them
- State is lifted: Form state lives in parent, not in field components
Every ObjectUI component receives a standard set of base props.
interface BaseComponentProps {
// Identification
id: string; // Unique component ID
name: string; // Field name (for form submission)
// Display
label?: string; // Display label
placeholder?: string; // Placeholder text
helpText?: string; // Helper text below field
tooltip?: string; // Tooltip on hover
icon?: string; // Lucide icon name
// State
value?: any; // Current value
defaultValue?: any; // Initial value
disabled?: boolean; // Disabled state
readonly?: boolean; // Read-only state
required?: boolean; // Required field marker
// Validation
error?: string; // Error message
warning?: string; // Warning message
valid?: boolean; // Validation state
// Layout
className?: string; // CSS classes
style?: CSSProperties; // Inline styles
span?: number; // Grid column span (1-12)
hidden?: boolean; // Visibility
// Accessibility
ariaLabel?: string; // ARIA label
ariaDescribedBy?: string; // ARIA description
tabIndex?: number; // Tab order
// Events
onChange?: (value: any) => void;
onBlur?: () => void;
onFocus?: () => void;
onClick?: () => void;
}# YAML Configuration
field:
name: customer_email
type: email
label: Email Address
placeholder: you@company.com
helpText: We'll never share your email
required: true
icon: mailRendered as (React):
<TextField
id="field_customer_email"
name="customer_email"
type="email"
label="Email Address"
placeholder="you@company.com"
helpText="We'll never share your email"
required={true}
icon="mail"
value={formData.customer_email}
error={errors.customer_email}
onChange={(value) => setFormData({ ...formData, customer_email: value })}
onBlur={() => validateField('customer_email')}
/>Different field types have specialized props.
interface TextFieldProps extends BaseComponentProps {
type: 'text' | 'email' | 'tel' | 'url' | 'password';
maxLength?: number; // Character limit
minLength?: number; // Minimum characters
pattern?: string; // Regex validation pattern
autocomplete?: string; // Browser autocomplete hint
spellcheck?: boolean; // Enable spell check
}Example:
field:
name: company_name
type: text
maxLength: 100
autocomplete: organization
spellcheck: trueinterface TextAreaProps extends BaseComponentProps {
rows?: number; // Visible rows
maxLength?: number; // Character limit
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
autoGrow?: boolean; // Auto-expand on content
}Example:
field:
name: description
type: textarea
rows: 5
maxLength: 1000
autoGrow: true
resize: verticalinterface SelectProps extends BaseComponentProps {
options: Option[]; // Available options
multiple?: boolean; // Multi-select
searchable?: boolean; // Filterable options
clearable?: boolean; // Show clear button
placeholder?: string; // Placeholder when empty
// Async options
optionsSource?: string; // URL to fetch options
optionsDependsOn?: string[]; // Refresh when these fields change
}
interface Option {
value: string | number;
label: string;
icon?: string;
disabled?: boolean;
group?: string; // Option group
}Example:
field:
name: country
type: select
options:
- { value: US, label: United States, icon: flag-us }
- { value: CA, label: Canada, icon: flag-ca }
- { value: UK, label: United Kingdom, icon: flag-gb }
searchable: true
clearable: trueinterface LookupProps extends BaseComponentProps {
object: string; // Target object name
displayField: string; // Field to display (e.g., 'name')
searchFields: string[]; // Fields to search
filters?: FilterExpression; // Pre-filter options
multiple?: boolean; // Multi-select
createNew?: boolean; // Allow creating new records
// Modal configuration
modalTitle?: string;
modalColumns?: string[]; // Columns to show in picker modal
}Example:
field:
name: account_id
type: lookup
object: account
displayField: name
searchFields: [name, email, phone]
filters:
status: active
createNew: true
modalTitle: Select Account
modalColumns: [name, industry, city]interface BooleanProps extends BaseComponentProps {
variant: 'checkbox' | 'toggle' | 'switch';
labelPosition?: 'left' | 'right';
trueLabel?: string; // Label when true (for toggles)
falseLabel?: string; // Label when false
}Example:
field:
name: email_notifications
type: boolean
variant: toggle
trueLabel: Enabled
falseLabel: Disabled
labelPosition: rightinterface DateProps extends BaseComponentProps {
type: 'date' | 'datetime' | 'time';
format?: string; // Display format (e.g., 'MM/DD/YYYY')
min?: string; // Minimum date
max?: string; // Maximum date
disabledDates?: string[]; // Specific disabled dates
showTime?: boolean; // Include time picker (for datetime)
timeFormat?: '12h' | '24h'; // Time format
}Example:
field:
name: meeting_date
type: datetime
format: MM/DD/YYYY hh:mm A
min: today
showTime: true
timeFormat: 12hinterface NumberProps extends BaseComponentProps {
type: 'number' | 'currency' | 'percent';
min?: number; // Minimum value
max?: number; // Maximum value
step?: number; // Increment step
precision?: number; // Decimal places
// Currency-specific
currency?: string; // ISO currency code (USD, EUR)
currencyDisplay?: 'symbol' | 'code' | 'name';
// Percent-specific
percentPrecision?: number; // Decimal places for percent
}Example:
field:
name: unit_price
type: currency
currency: USD
min: 0
precision: 2
currencyDisplay: symbolAll components emit standard lifecycle and interaction events.
interface ComponentEvents {
// Value changes
onChange: (value: any, context: EventContext) => void;
onInput: (value: any) => void; // Real-time input (every keystroke)
// Focus events
onFocus: (context: EventContext) => void;
onBlur: (context: EventContext) => void;
// Interaction events
onClick: (context: EventContext) => void;
onDoubleClick: (context: EventContext) => void;
// Validation events
onValidate: (value: any) => ValidationResult;
onError: (error: ValidationError) => void;
// Lifecycle events
onMount: () => void;
onUnmount: () => void;
}
interface EventContext {
fieldName: string;
value: any;
previousValue: any;
formData: Record<string, any>;
metadata: Record<string, any>;
}onChange: Field Value Changed
field:
name: quantity
type: number
onChange:
- action: calculate_total
formula: quantity * unit_price
targetField: total_priceonBlur: Field Loses Focus
field:
name: email
type: email
onBlur:
- action: validate_email
- action: check_duplicates
endpoint: /api/customers/check-emailConditional Logic on Change
field:
name: shipping_required
type: boolean
onChange:
- when: { value: true }
show: [shipping_address, shipping_method]
- when: { value: false }
hide: [shipping_address, shipping_method]
clear: [shipping_address, shipping_method]Components support declarative validation rules.
interface FieldValidation {
// Presence
required?: boolean | string; // true or custom error message
// String validation
minLength?: number;
maxLength?: number;
pattern?: RegExp;
email?: boolean;
url?: boolean;
// Number validation
min?: number;
max?: number;
integer?: boolean;
positive?: boolean;
// Async validation
asyncValidator?: {
endpoint: string;
debounce?: number; // Debounce delay (ms)
};
// Custom validation
custom?: (value: any, formData: any) => ValidationResult;
}
interface ValidationResult {
valid: boolean;
error?: string;
warning?: string;
}Required Field
field:
name: customer_name
type: text
required: Customer name is requiredPattern Validation
field:
name: phone
type: tel
pattern: ^\+?[1-9]\d{1,14}$
patternMessage: Please enter a valid phone number (E.164 format)Async Validation (Check Uniqueness)
field:
name: username
type: text
asyncValidator:
endpoint: /api/users/check-username
debounce: 500 # Wait 500ms after typing stops
errorMessage: Username already takenCross-Field Validation
field:
name: password_confirm
type: password
custom:
validate: password_confirm === password
errorMessage: Passwords must matchRenderers must implement specific methods to paint components.
interface ObjectUIRenderer {
// Core rendering
renderField(field: FieldDefinition): ReactNode;
renderSection(section: SectionDefinition): ReactNode;
renderForm(form: FormDefinition): ReactNode;
// Component registry
registerComponent(type: string, component: ComponentClass): void;
getComponent(type: string): ComponentClass | undefined;
// Lifecycle hooks
beforeRender(definition: ComponentDefinition): void;
afterRender(element: ReactNode): void;
// Error handling
renderError(error: Error, definition: ComponentDefinition): ReactNode;
renderFallback(type: string): ReactNode; // Unknown component type
}Custom components can be registered with the renderer:
// Define custom component
const CustomRatingField: React.FC<BaseComponentProps> = ({ value, onChange }) => {
return (
<div className="rating-field">
{[1, 2, 3, 4, 5].map(star => (
<Star
key={star}
filled={value >= star}
onClick={() => onChange(star)}
/>
))}
</div>
);
};
// Register with renderer
renderer.registerComponent('rating', CustomRatingField);Use in Layout:
field:
name: customer_satisfaction
type: rating # ← Custom type
label: How satisfied are you?
min: 1
max: 5Components must support theme customization.
interface ThemeProps {
// Colors
colorScheme?: 'light' | 'dark' | 'auto';
primaryColor?: string;
secondaryColor?: string;
errorColor?: string;
warningColor?: string;
successColor?: string;
// Typography
fontFamily?: string;
fontSize?: 'small' | 'medium' | 'large';
// Spacing
density?: 'compact' | 'default' | 'spacious';
// Borders
borderRadius?: number; // px
borderStyle?: 'solid' | 'dashed' | 'none';
// Variants
variant?: 'outlined' | 'filled' | 'standard';
}theme:
colorScheme: light
primaryColor: '#3B82F6'
errorColor: '#EF4444'
fontFamily: Inter, sans-serif
fontSize: medium
density: default
borderRadius: 8
variant: outlined
fields:
- name: email
type: email
# Inherits theme automaticallyAll components must meet WCAG 2.1 AA standards.
interface A11yProps {
// ARIA attributes
role?: string; // ARIA role
ariaLabel?: string; // Accessible label
ariaDescribedBy?: string; // ID of description element
ariaLive?: 'polite' | 'assertive' | 'off';
ariaAtomic?: boolean;
// Keyboard navigation
tabIndex?: number; // Tab order (-1 = not tabbable)
onKeyDown?: (e: KeyboardEvent) => void;
onKeyUp?: (e: KeyboardEvent) => void;
// Focus management
autoFocus?: boolean; // Auto-focus on mount
focusOnError?: boolean; // Focus when error occurs
}field:
name: credit_card
type: text
label: Credit Card Number
ariaLabel: Credit card number, 16 digits
ariaDescribedBy: credit_card_help
helpText:
id: credit_card_help
text: Enter 16-digit card number without spaces
autocomplete: cc-number
inputMode: numericKeyboard Support (Required):
- Tab: Move to next field
- Shift+Tab: Move to previous field
- Enter: Submit form (on last field)
- Escape: Cancel/clear (if applicable)
- Arrow keys: Navigate options (Select, Radio)
- Space: Toggle (Checkbox, Toggle)
Components should optimize rendering and minimize re-renders.
interface PerformanceProps {
// Lazy loading
lazy?: boolean; // Defer rendering until visible
lazyPlaceholder?: ReactNode; // Show while loading
// Memoization
memoize?: boolean; // Cache rendered output
memoizeKey?: string[]; // Re-render only when these props change
// Virtualization
virtualScroll?: boolean; // Use virtual scrolling for long lists
itemHeight?: number; // Fixed item height (for virtual scroll)
// Debouncing
debounce?: number; // Debounce onChange events (ms)
throttle?: number; // Throttle onChange events (ms)
}Example: Large Select with Virtual Scrolling
field:
name: product_id
type: select
optionsSource: /api/products # 10,000 products
virtualScroll: true
itemHeight: 40
searchable: true
debounce: 300 # Debounce search inputComponents must gracefully handle errors and edge cases.
interface ErrorHandling {
// Validation errors
error?: string; // Error message
errorPosition?: 'top' | 'bottom' | 'inline';
// Loading states
loading?: boolean; // Show loading indicator
loadingText?: string; // Loading message
// Empty states
empty?: boolean; // No data available
emptyText?: string; // Empty state message
emptyAction?: Action; // Call-to-action when empty
// Error recovery
retry?: () => void; // Retry failed action
fallback?: any; // Fallback value on error
}Example: Lookup with Error Handling
field:
name: manager_id
type: lookup
object: user
displayField: name
optionsSource: /api/users/managers
loading: true
loadingText: Loading managers...
emptyText: No managers found
emptyAction:
type: standard_new
label: Create Manager
retry: true # Show retry button on fetch errorComponents go through a standard lifecycle.
1. Initialize
↓
2. Mount (render for first time)
↓
3. Update (props/state change)
↓
4. Validate (on blur or submit)
↓
5. Unmount (cleanup)
interface LifecycleHooks {
onInit?: () => void; // Before first render
onMount?: () => void; // After first render
onUpdate?: (prevProps: Props) => void; // After props change
onValidate?: () => ValidationResult; // On validation
onUnmount?: () => void; // Before component removed
}Example:
field:
name: email
type: email
lifecycle:
onMount:
- action: fetch_user_email
endpoint: /api/user/email
onBlur:
- action: validate_email
- action: check_duplicates
onUnmount:
- action: save_draft| Type | Props | Events | Example |
|---|---|---|---|
text |
maxLength, pattern | onChange, onBlur | Name, Title |
textarea |
rows, maxLength | onChange, onInput | Description |
email |
pattern | onChange, onBlur | Email Address |
tel |
pattern | onChange | Phone Number |
url |
pattern | onChange | Website |
password |
minLength | onChange | Password |
| Type | Props | Events | Example |
|---|---|---|---|
select |
options, multiple | onChange | Country, Status |
radio |
options | onChange | Gender, Size |
checkbox |
- | onChange | Accept Terms |
toggle |
trueLabel, falseLabel | onChange | Enable Feature |
lookup |
object, displayField | onChange, onSearch | Account, Contact |
| Type | Props | Events | Example |
|---|---|---|---|
date |
min, max | onChange | Birth Date |
datetime |
showTime | onChange | Appointment |
time |
format | onChange | Meeting Time |
daterange |
- | onChange | Start/End Date |
| Type | Props | Events | Example |
|---|---|---|---|
number |
min, max, step | onChange | Quantity, Age |
currency |
currency, precision | onChange | Price, Revenue |
percent |
precision | onChange | Discount, Tax |
slider |
min, max, step | onChange | Volume, Rating |
| Type | Props | Events | Example |
|---|---|---|---|
richtext |
toolbar, plugins | onChange | Description |
markdown |
preview | onChange | Documentation |
code |
language, theme | onChange | API Key, JSON |
file |
accept, maxSize | onUpload | Attachment |
field:
# Identification
name: customer_email
type: email
# Display
label: Email Address
placeholder: you@company.com
helpText: We'll use this for account notifications
icon: mail
# Validation
required: Email is required
pattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$
patternMessage: Please enter a valid email address
asyncValidator:
endpoint: /api/customers/check-email
debounce: 500
errorMessage: This email is already registered
# Layout
span: 6
# Behavior
autocomplete: email
spellcheck: false
# Events
onChange:
- action: validate_email
onBlur:
- action: check_duplicates
- action: format_email
transform: toLowerCase
# Accessibility
ariaLabel: Customer email address
tabIndex: 1
# Performance
debounce: 300- FormView Reference - Form configuration API
- Field Types Reference - Complete field type catalog
- Validation Guide - Validation patterns and examples
- Accessibility Guide - WCAG compliance checklist