Docs/uiv1.0.0-rc.1

UI Component System Overview

Overview

UDIOT ships a set of server-renderable UI components (Button, Card, Input, Modal, DragDropManager, SortableList) alongside a UIComponentRegistry for component registration and discovery. Components extend BaseComponent (from src/components.ts) and are decorated with @Component({ selector }). They use @InjectProperty() to receive services such as ThemeService and ValidationService from the DI container, and optionally accept an AIStyler instance for AI-generated CSS.

Key Concepts

  • BaseComponent<TProps> — base class for all UI components. Provides a props accessor, an onInit() lifecycle hook, and an abstract render(): string method that returns an HTML string.
  • @Component({ selector }) — class decorator that registers the component selector (e.g. udiot-button) so it can be discovered by the renderer.
  • @InjectProperty() — property decorator that wires the DI container into a component field at instantiation time. Used for ThemeService, ValidationService, and any other injectable service.
  • ThemeService — service (from src/services/ui/) that exposes getTheme(): 'light' | 'dark' and subscribe(callback) for reactive theme changes.
  • AIStyler — optional styling bridge (from src/styling.ts) that generates component CSS by calling an AI model. When omitted, components use built-in theme-aware fallback styles.
  • UIComponentRegistry — singleton componentRegistry that maps component names to ComponentConfig records, including an optional AIClient and AIStyler for AI-driven component generation.
  • createXxx factories — each component module exports a factory function (e.g. createButton(props)) as a convenience alternative to calling new Button(props).

DI integration and limitations

Public components use internal theme/validation services. Those service classes are not public /services/ui or /services/api imports. Do not assume that constructing a component wires them automatically or that a validationRules prop enforces server-side validation. Use application-owned validation and verify component behavior with your chosen lifecycle/DI integration.

The root DI component decorator is named ComponentDecorator; Component is an interface. The UI helpers described here, including Modal and the registry, are not exported by @tagna/udiot/browser. Do not import the root entry into client bundles or assume server-rendered markup automatically becomes interactive. Use application-owned client UI behavior. Components and generated styling remain experimental.

AI Styling

Pass an AIStyler instance as the aiStyler prop to have the component's CSS generated at render time by an AI model:

import { createButton } from '@tagna/udiot';
import { AIStyler } from '@tagna/udiot';

const styler = new AIStyler(aiClient);
const button = createButton({
  children: 'Save',
  variant: 'primary',
  aiStyler: styler,
});
console.log(button.render()); // <style>/* AI-generated CSS */</style><button ...>Save</button>

When aiStyler is omitted, the component generates its own styles using CSS variables derived from ThemeService.getTheme().

UIComponentRegistry

interface ComponentConfig {
  name: string;
  description: string;
  aiClient?: AIClient;
  styler?: AIStyler;
  props?: Record<string, any>;
}

class UIComponentRegistry {
  register(config: ComponentConfig): void;
  get(name: string): ComponentConfig | undefined;
  list(): string[];
  generateComponent(name: string, description: string, aiClient: AIClient): Promise<string>;
}

// Public singleton; register application configurations explicitly
const componentRegistry: UIComponentRegistry;

The root export exposes componentRegistry but does not import the internal UI auto-registration barrel. Register application component configurations explicitly. Modal and DragDropManager are not pre-registered (they have a different instantiation pattern) but can be added manually.

When to Use UDIOT Components vs External Libraries

Scenario Recommendation
Server-rendered HTML with SSR/SSG UDIOT components — render() returns a plain string
Theme-aware styling without a separate CSS framework UDIOT components — built-in light/dark support via ThemeService
Accessibility-critical forms UDIOT Input — includes aria-invalid, aria-describedby, label association
Complex client-side interactions (date pickers, rich-text) Use an external library and hydrate with a ComponentHydrator
Drag-and-drop sortable lists Use application-owned browser code; root UI helpers lack a public browser-safe entry

Basic Usage

import { createButton, createCard, createInput, componentRegistry } from '@tagna/udiot';

// Render a button
const btn = createButton({ children: 'Submit', variant: 'primary', size: 'large' });
const html = btn.render();

// List registered components
console.log(componentRegistry.list()); // [] before application registrations

// Register a custom component
componentRegistry.register({
  name: 'MyWidget',
  description: 'Custom widget with special behaviour',
});

Integration Notes

  • Components render to HTML strings — they are not virtual DOM elements. They are designed to be composed inside SSR page render() methods or used standalone in template engines.
  • onInit() is called by the BaseComponent constructor after this.props is set. Service injection via @InjectProperty() is resolved before the constructor returns only when the DI container bootstraps the component. In manual instantiation, @InjectProperty() fields will be undefined unless you set them explicitly.
  • generateStyles() is called in the constructor (synchronously) and again whenever ThemeService fires a change event. Because AIStyler.generateComponentStyles() is async, the first synchronous call may complete before the AI response arrives; a second re-render will pick up the generated styles.
  • All built-in component CSS is scoped by class names (button, card, input-group, etc.) rather than shadow DOM. Avoid global styles that override these class names.

Pitfalls

  • Do not use createButton and similar factories in browser-side code that requires reactivity. These components produce a static HTML string; re-rendering requires calling render() again and replacing the DOM node.
  • ThemeService is an optional dependency. If it is not provided by the DI container, components fall back to 'light' theme styles. Always check that ThemeService is registered in your DI module if you need dark-mode support.
  • AIStyler.generateComponentStyles() is asynchronous and calls an external AI API. It should not be used in production SSR without a caching layer — each render will make a network call, significantly increasing response latency.
  • componentRegistry.generateComponent() returns AI-generated TypeScript source code as a string. It does not register or compile the component; you must evaluate or write the string to disk yourself.
From the canonical UDIOT public documentation.Try it in the playground ↗