ObjectUIObjectUI

Component Registry

The Component Registry is Object UI's system for mapping schema types to React components. Understanding the registry is key to extending Object UI with custom components.

Overview

The registry acts as a lookup table that the SchemaRenderer uses to determine which React component to render for each schema type:

Schema Type → Component Registry → React Component

Getting the Registry

import { getComponentRegistry } from '@object-ui/react'

const registry = getComponentRegistry()

Registering Components

Using Default Components

The easiest way to get started is to register all default components:

import { registerDefaultRenderers } from '@object-ui/components'

// Call once at app initialization
registerDefaultRenderers()

This registers all built-in components like:

  • Forms: input, textarea, select, checkbox, etc.
  • Data: table, list, card, tree, etc.
  • Layout: page, grid, flex, container, etc.
  • Feedback: alert, dialog, toast, etc.

Registering Individual Components

Register specific components one at a time:

import { getComponentRegistry } from '@object-ui/react'
import { InputRenderer } from '@object-ui/components'

const registry = getComponentRegistry()

registry.register('input', InputRenderer)

Registering Custom Components

Create and register your own components:

import { getComponentRegistry } from '@object-ui/react'
import type { BaseSchema } from '@object-ui/core'

interface MyComponentSchema extends BaseSchema {
  type: 'my-component'
  title: string
  content: string
}

function MyComponent(props: MyComponentSchema) {
  return (
    <div className="my-component">
      <h3>{props.title}</h3>
      <p>{props.content}</p>
    </div>
  )
}

const registry = getComponentRegistry()
registry.register('my-component', MyComponent)

Now you can use it in schemas:

{
  "type": "my-component",
  "title": "Hello",
  "content": "This is my custom component!"
}

Component Interface

All registered components receive the schema as props:

interface ComponentProps<T extends BaseSchema = BaseSchema> {
  // The complete schema object
  schema: T
  
  // Data context (optional)
  data?: Record<string, any>
  
  // Event handlers (optional)
  onAction?: (action: any, context: any) => void
  onChange?: (value: any) => void
  onSubmit?: (data: any) => void
}

function MyRenderer(props: ComponentProps<MyComponentSchema>) {
  const { schema, data, onChange } = props
  
  return (
    <div className={schema.className}>
      {/* Your component implementation */}
    </div>
  )
}

Advanced Registration

With Metadata

Register components with additional metadata:

registry.register('my-component', MyComponent, {
  displayName: 'My Custom Component',
  category: 'Custom',
  icon: 'component-icon',
  description: 'A custom component for special use cases',
  schema: {
    type: 'object',
    properties: {
      title: { type: 'string' },
      content: { type: 'string' }
    }
  }
})

This metadata is used by the Visual Designer to provide better editing experience.

Lazy Loading

Register components that load on demand:

import { lazy } from 'react'

const HeavyComponent = lazy(() => import('./HeavyComponent'))

registry.register('heavy-component', HeavyComponent, {
  lazy: true
})

Overriding Built-in Components

Override default components with your own:

import { registerDefaultRenderers } from '@object-ui/components'

// Register defaults first
registerDefaultRenderers()

// Override specific component
registry.register('button', MyCustomButton)

Component Categories

Default components are organized by category:

Form Components

- input
- textarea
- select
- checkbox
- radio
- switch
- slider
- date-picker
- time-picker
- file-upload
- color-picker

Data Display

- table
- list
- card
- tree
- timeline
- calendar
- kanban

Layout

- page
- container
- grid
- flex
- tabs
- accordion
- divider
- spacer

Feedback

- alert
- toast
- dialog
- drawer
- popover
- tooltip
- progress
- skeleton
- spinner
- menu
- breadcrumb
- pagination
- steps

Other

- button
- link
- text
- icon
- image
- video
- badge
- avatar

Checking Registered Components

Get All Registered Types

const types = registry.getRegisteredTypes()
console.log(types) // ['input', 'button', 'form', ...]

Check if Type is Registered

if (registry.has('my-component')) {
  console.log('Component is registered')
}

Get Component Metadata

const metadata = registry.getMetadata('input')
console.log(metadata)
// {
//   displayName: 'Input',
//   category: 'Form',
//   icon: 'input-icon',
//   ...
// }

Best Practices

1. Register Once at App Initialization

// main.tsx or App.tsx
import { registerDefaultRenderers } from '@object-ui/components'

registerDefaultRenderers()

function App() {
  // Your app code
}

2. Use TypeScript for Custom Components

import type { BaseSchema } from '@object-ui/core'

interface CustomSchema extends BaseSchema {
  type: 'custom'
  customProp: string
}

function CustomComponent(props: { schema: CustomSchema }) {
  // TypeScript ensures type safety
}

3. Follow Naming Conventions

Use kebab-case for component types:

  • my-component, custom-button, data-table
  • MyComponent, customButton, DataTable

4. Provide Meaningful Metadata

registry.register('rating', RatingComponent, {
  displayName: 'Star Rating',
  category: 'Form',
  icon: 'star',
  description: 'A 5-star rating input component',
  tags: ['form', 'input', 'rating']
})

5. Handle Missing Props Gracefully

function MyComponent(props: ComponentProps<MySchema>) {
  const { schema } = props
  const title = schema.title || 'Default Title'
  const content = schema.content || ''
  
  return (
    <div>
      <h3>{title}</h3>
      <p>{content}</p>
    </div>
  )
}

Creating Plugin Packages

Group related components into plugin packages:

// @my-org/objectui-plugin-charts
import { getComponentRegistry } from '@object-ui/react'
import { BarChart } from './BarChart'
import { LineChart } from './LineChart'
import { PieChart } from './PieChart'

export function registerChartComponents() {
  const registry = getComponentRegistry()
  
  registry.register('bar-chart', BarChart)
  registry.register('line-chart', LineChart)
  registry.register('pie-chart', PieChart)
}

Usage:

import { registerDefaultRenderers } from '@object-ui/components'
import { registerChartComponents } from '@my-org/objectui-plugin-charts'

registerDefaultRenderers()
registerChartComponents()

Example: Custom Form Component

Here's a complete example of a custom form component:

import { forwardRef } from 'react'
import { getComponentRegistry } from '@object-ui/react'
import type { BaseSchema } from '@object-ui/core'
import { cn } from '@/lib/utils'

interface RatingSchema extends BaseSchema {
  type: 'rating'
  name: string
  label?: string
  maxStars?: number
  required?: boolean
  disabled?: boolean
  onChange?: (value: number) => void
}

const RatingComponent = forwardRef<HTMLDivElement, { schema: RatingSchema }>(
  ({ schema }, ref) => {
    const [value, setValue] = useState(0)
    const maxStars = schema.maxStars || 5

    const handleClick = (rating: number) => {
      if (schema.disabled) return
      setValue(rating)
      schema.onChange?.(rating)
    }

    return (
      <div ref={ref} className={cn('flex flex-col gap-2', schema.className)}>
        {schema.label && (
          <label className="text-sm font-medium">
            {schema.label}
            {schema.required && <span className="text-red-500">*</span>}
          </label>
        )}
        <div className="flex gap-1">
          {Array.from({ length: maxStars }).map((_, index) => (
            <button
              key={index}
              type="button"
              onClick={() => handleClick(index + 1)}
              disabled={schema.disabled}
              className={cn(
                'text-2xl transition-colors',
                index < value ? 'text-yellow-400' : 'text-gray-300',
                !schema.disabled && 'hover:text-yellow-300 cursor-pointer'
              )}
            >

            </button>
          ))}
        </div>
      </div>
    )
  }
)

RatingComponent.displayName = 'Rating'

// Register the component
const registry = getComponentRegistry()
registry.register('rating', RatingComponent, {
  displayName: 'Star Rating',
  category: 'Form',
  description: 'A star rating input component',
  schema: {
    type: 'object',
    properties: {
      name: { type: 'string' },
      label: { type: 'string' },
      maxStars: { type: 'number', default: 5 },
      required: { type: 'boolean' },
      disabled: { type: 'boolean' }
    },
    required: ['name']
  }
})

export { RatingComponent }

Next Steps

On this page