Skip to main content
Wildo.ai Coming soon

Components and theming

Replace the parts that make your product different

Replace a registered component while keeping the screens and contracts that use it.

A custom component replaces one part within a shared screen.

Replace the parts that make your product different

Wildo’s replaceable components have named slots: a button, a field control or a layout can resolve a registered implementation instead of importing it directly.

Keep the supplied implementation where it fits. Register an alternative where your product needs something different, while preserving the contract used by its callers.

Example — A specialist view inside the application

A planning product adds a focus view for its own workflow. The application registers that view under a stable name, and the configured launcher refers to that name. Standard controls elsewhere keep their existing implementations.

For engineers

Wonder Todos registers a custom focus view in frontend/src/modules/example-dev/index.ts. This is the actual selected registration; the surrounding module also contributes its view definitions and shell placement.

    {
      componentRef: FOCUS_HUB_SMOKE_COMPONENT_REF,
      loadComponent: async () => {
        const module = await import('./app-level-views/FocusHubSmokeView.js');
        return module.FocusHubSmokeView;
      },
      preset: CorePresetNames.DEFAULT,
      isConfigurable: false,
    },

componentRef must agree with the view definition or wrapper that requests it. loadComponent returns the React implementation. preset selects the registry key; a replacement must meet the props and ref contract expected by the existing wrapper.

The application’s modules-registry.frontend.ts then registers defaults before its module contributions:

    componentLoader: async () => {
      await registerFrameworkFrontendComponents();
      await registerFrontendComponentRegistrations(frontendModuleRegistry.componentRegistrations);
    },

The module belongs in applicationFrontendModules, which feeds buildFrontendModuleRegistry. The resulting componentLoader is handed to application initialization. These asynchronous imports may form separate bundles, but this loader awaits them at startup; they are not automatically deferred until a screen opens.

Replace the edit category without replacing display

An existing resource-reference field uses one slot with separate EDIT and DISPLAY registrations. This illustrative adapter preserves the existing picker’s behavior while adding a local styling hook. It captures the implementation after defaults load, then replaces only the edit key. The public props and forwarded ref remain intact.

import { forwardRef } from 'react';
import {
  ComponentRegistryService,
  type ResourceFieldEditPreset_ResourceReference_DefaultProps as PickerProps,
  type ResourceFieldEdit_ResourceReferenceRef as PickerRef,
} from '@wildo-ai/saas-frontend-lib';
import {
  FrontendComponentType, CorePresetNames, FrontEndComponentResourceFieldCategory,
} from '@wildo-ai/presets-components-models';

export function registerApplicationPicker(): void {
  const slot = FrontendComponentType.RESOURCES_FIELDS_RESOURCE_REFERENCE;
  const preset = CorePresetNames.DEFAULT;
  const category = FrontEndComponentResourceFieldCategory.EDIT;
  const Original = ComponentRegistryService.resolve(slot, preset, category);
  if (!Original) throw new Error('Register framework components before the picker adapter');

  const ApplicationPicker = forwardRef<PickerRef, PickerProps>((props, ref) => (
    <Original
      {...props}
      ref={ref}
      className={['application-reference-picker', props.className].filter(Boolean).join(' ')}
    />
  ));
  ApplicationPicker.displayName = 'ApplicationPicker';

  ComponentRegistryService.register(slot, preset, ApplicationPicker, {
    category,
    isConfigurable: true,
  });
}

Call registerApplicationPicker() once at the end of the application’s component loader, after framework defaults and module registrations. The generated field’s ResourceFieldEdit_ResourceReference wrapper then resolves this exact slot/preset/EDIT key. Its display wrapper continues resolving DISPLAY, which was not changed. The class is an application-owned hook; define its styling in the application’s stylesheet.

Capturing Original before registering avoids resolving the adapter from inside itself. Do not render the public resolving wrapper inside its own replacement: that would recurse. A completely new picker may replace the captured implementation, but must preserve value changes, disabled/error behavior and the forwarded input/button ref. isConfigurable is metadata, not an overwrite lock; the last registration for a key wins.

Choose the replacement boundary deliberately

ChoiceMeaning
Existing slot and presetReplace that registration; the last registration for the same key wins
Another preset on the slotOffer an explicitly selected alternative
New component referenceAdd a custom view with its own caller and definition
A framework-owned direct rendererUse its supported configuration or extension point; its named specification slot alone does not make it injectable

Direct registry keys also include a field category where applicable. Module contributions merge by componentRef, so two module entries with that same reference do not preserve independent presets merely because their preset names differ.

A missing requested preset does not silently use Default: the resolver shows a development placeholder and renders no component in production. Keep the startup completeness check and exercise the exact preset your screen requests.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.