UIComponentRegistry
Overview
UIComponentRegistry is a named-component catalogue that maps string names to ComponentConfig records containing metadata, an optional AIClient, and an optional AIStyler. The public root exports an initially empty singleton componentRegistry. Register configurations explicitly during application startup; importing component factories does not register them. These root imports are for Node-side code, not browser bundles.
Key Concepts
UIComponentRegistry— aMap-backed registry.register()adds or overwrites an entry;get()looks up an entry by name;list()returns all registered names.ComponentConfig— the metadata record stored per component:name,description, an optionalaiClient, an optionalstyler, and optional defaultprops.generateComponent— callsaiClient.generateCode(prompt)with a prompt that instructs the AI to produce a TypeScript component source file. Returns the generated source as a string.componentRegistry— the module-level singleton instance. The public root does not run the internal UI auto-registration barrel; register configurations explicitly.
API Reference
ComponentConfig
interface ComponentConfig {
name: string; // Unique component name (e.g. 'Button')
description: string; // Human-readable description used in AI generation prompts
aiClient?: AIClient; // AI client for code generation (optional)
styler?: AIStyler; // AI styling bridge for CSS generation (optional)
props?: Record<string, any>; // Default props snapshot (optional)
}
UIComponentRegistry
class UIComponentRegistry {
// Register or overwrite a component entry
register(config: ComponentConfig): void;
// Look up a component entry by name; returns undefined if not found
get(name: string): ComponentConfig | undefined;
// List all registered component names
list(): string[];
// Generate a new component's TypeScript source via AI
generateComponent(
name: string,
description: string,
aiClient: AIClient
): Promise<string>;
}
Singleton
// Initially empty; populated by application startup code
const componentRegistry: UIComponentRegistry;
generateComponent prompt
The method constructs this prompt internally:
Create a TypeScript React-like component called <name> that <description>.
Include proper TypeScript types, props interface, and modern best practices.
The return value is the raw string returned by aiClient.generateCode(prompt).
Basic Usage
Listing registered components
import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';
// In a fresh process, before any application registrations:
assert.deepEqual(componentRegistry.list(), []);
for (const name of ['Button', 'Card', 'Input']) {
componentRegistry.register({ name, description: `${name} UI component` });
}
assert.deepEqual(componentRegistry.list(), ['Button', 'Card', 'Input']);
Registering a custom component
import { componentRegistry } from '@tagna/udiot';
componentRegistry.register({
name: 'Tooltip',
description: 'A floating tooltip component with arrow indicator and positioning logic',
});
const config = componentRegistry.get('Tooltip');
console.log(config?.description);
// 'A floating tooltip component with arrow indicator and positioning logic'
Registering with an AIStyler
import { componentRegistry } from '@tagna/udiot';
import { AIStyler, AIClient } from '@tagna/udiot';
const aiClient = new AIClient({ apiKey: process.env.AI_API_KEY! });
const styler = new AIStyler(aiClient);
componentRegistry.register({
name: 'HeroSection',
description: 'A full-width hero banner with background image and CTA button',
aiClient,
styler,
props: { headline: 'Default Headline', ctaLabel: 'Get Started' },
});
Generating a new component with AI
import { componentRegistry } from '@tagna/udiot';
import { AIClient } from '@tagna/udiot';
import { writeFile } from 'fs/promises';
const aiClient = new AIClient({ apiKey: process.env.AI_API_KEY! });
const source = await componentRegistry.generateComponent(
'PricingTable',
'displays three subscription tiers with feature lists, pricing, and a highlighted recommended tier',
aiClient
);
// Write generated source to disk
await writeFile('./src/ui/PricingTable.ts', source, 'utf-8');
console.log('Generated PricingTable component');
Dynamic component rendering pattern
import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';
import { createButton, createCard, createInput } from '@tagna/udiot';
const componentFactories: Record<string, (props: any) => { render(): string }> = {
Button: (props) => createButton(props),
Card: (props) => createCard(props),
Input: (props) => createInput(props),
};
// Metadata and factories are separate; configure both in this example.
for (const name of Object.keys(componentFactories)) {
componentRegistry.register({ name, description: `${name} UI component` });
}
function renderComponent(name: string, props: Record<string, any>): string {
if (!componentRegistry.get(name)) {
throw new Error(`Unknown component: ${name}`);
}
const factory = componentFactories[name];
if (!factory) {
throw new Error(`No factory registered for: ${name}`);
}
return factory(props).render();
}
// Usage
const html = renderComponent('Button', { children: 'Click me', variant: 'primary' });
assert.ok(html.includes('Click me'));
assert.throws(() => renderComponent('Unknown', {}), /Unknown component/);
Common Patterns
Component catalogue endpoint
import { componentRegistry } from '@tagna/udiot';
// In an API route handler — returns available components for a design tool
function getComponentCatalogue() {
return componentRegistry.list().map(name => {
const config = componentRegistry.get(name)!;
return {
name: config.name,
description: config.description,
hasAI: !!config.aiClient,
defaultProps: config.props ?? {},
};
});
}
Updating an explicitly registered configuration
import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';
componentRegistry.register({ name: 'Button', description: 'Application button' });
const existing = componentRegistry.get('Button');
assert.ok(existing);
componentRegistry.register({ ...existing, props: { variant: 'secondary' } });
assert.equal(componentRegistry.get('Button')?.props?.variant, 'secondary');
Integration Notes
componentRegistryis a module-level singleton. It is shared across all imports of@tagna/udiotwithin the same Node.js process. Registrations from one module are visible to all others.register()overwrites silently. If you register a name that already exists, the previousComponentConfigis replaced without warning.generateComponent()returns a TypeScript source string. It does not compile, evaluate, import, or register the generated component automatically. You are responsible for writing the output to disk and including it in your build.generateComponentuses its explicitaiClientargument, not the client stored in a configuration. Stored clients and stylers are metadata for application code; the registry does not automatically attach them to rendered components.
Pitfalls
componentRegistry.get(name)returnsundefinedfor unregistered names. Always guard the return value before accessing its properties.- The
generateComponentprompt is fixed — it cannot be customised. If you need a tailored prompt, callaiClient.generateCode(yourPrompt)directly instead of usinggenerateComponent. componentRegistryis not cleared between requests in SSR. Registrations made during one request persist for the lifetime of the process. Avoid registering user-supplied component names — they will affect all subsequent requests.propsinComponentConfigis a documentation/default-value hint. It is not used by any framework machinery — you must pass actual props to component factories (e.g.createButton(props)) separately.
From the canonical UDIOT public documentation.Try it in the playground ↗