# Custom Components

> Web SDK provides multiple ways to integrate custom components, allowing you to extend and customize its functionality. This guide will introduce several common methods.

- Human documentation: [https://docs.univer.ai/guides/docs/ui/components](https://docs.univer.ai/guides/docs/ui/components)

- Agent Markdown: [https://docs.univer.ai/guides/docs/ui/components.md](https://docs.univer.ai/guides/docs/ui/components.md)

- Requested language: `en-US`

- Content language: `en-US`

- Documentation version: `1.0.4`

- Source: [docs/ui/components.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/docs/ui/components.mdx)

---

Univer does not directly pass components as parameters to any rendering functions. You need to register the components with Univer through the Facade API before you can use them in various mounting points.

If you are not familiar with how to obtain an instance of the Facade API, you can refer to [Facade API](https://docs.univer.ai/guides/docs/getting-started/facade.md).

## Registering Custom Components

```typescript
// [!code word:componentKey]
// [!code word:CustomComponent]
// [!code word:options]
univerAPI.registerComponent(componentKey, CustomComponent, options)
```

Use the `univerAPI.registerComponent` method to register custom components. This method accepts three parameters:

* `componentKey`: A unique identifier for the component, used to reference it within Univer.
* `CustomComponent`: The implementation of the component, which can be a React, Vue, or Web Component.
* `options`: Optional configuration options that can be used to specify the framework the component depends on or other related settings.

### React Components

To register a React component, no additional configuration is required; just ensure that the component is a valid React component. Here is a simple example:

```tsx
function ReactComponent(props: Record<string, any>) {
  return <div>Hello Univer!</div>
}

univerAPI.registerComponent('MyReactComponent', ReactComponent)
```

### Vue Components

#### Vue 3.x

If you want to register Vue components, make sure to install and register the `UniverVue3AdapterPlugin` from the `@univerjs/ui-adapter-vue3` package:

#### npm

```bash
npm install @univerjs/ui-adapter-vue3
```

#### pnpm

```bash
pnpm add @univerjs/ui-adapter-vue3
```

#### yarn

```bash
yarn add @univerjs/ui-adapter-vue3
```

#### bun

```bash
bun add @univerjs/ui-adapter-vue3
```

```typescript
import { UniverVue3AdapterPlugin } from '@univerjs/ui-adapter-vue3'

univer.registerPlugin(UniverVue3AdapterPlugin)
```

When registering Vue components, you need to specify the `framework` option as `'vue3'`:

```tsx
const Vue3Component = defineComponent({
  setup(props) {
    return () => <div>Hello Univer!</div>
  },
})

univerAPI.registerComponent('MyVue3Component', Vue3Component, {
  // [!code word:vue3]
  framework: 'vue3',
})
```

#### Vue 2.x

Since Vue 2.x is EOL (End of Life) and no longer maintained, Univer does not currently provide a UI adapter plugin for Vue 2.x. However, you can implement a Vue 2.x adapter by creating a custom plugin. Below is an example of how to create a Vue 2.x adapter plugin:

```typescript title="ui-adapter-vue2.ts"
import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'
import { ComponentManager, UniverUIPlugin } from '@univerjs/ui'
import Vue from 'vue'

/**
 * The plugin that allows Univer to use Vue 2 components as UI components.
 */
@DependentOn(UniverUIPlugin)
export class UniverVue2AdapterPlugin extends Plugin {
  static override pluginName = 'UNIVER_UI_VUE2_ADAPTER_PLUGIN'

  constructor(
    private readonly _config = {},
    @Inject(Injector) protected readonly _injector: Injector,
    @Inject(ComponentManager) protected readonly _componentManager: ComponentManager,
  ) {
    super()
  }

  override onStarting(): void {
    const { createElement, useEffect, useRef } = this._componentManager.reactUtils

    this._componentManager.setHandler('vue2', (component: any) => {
      return (props: Record<string, any>) =>
        createElement(VueComponentWrapper, {
          component,
          props: Object.keys(props).reduce<Record<string, any>>((acc, key) => {
            if (key !== 'key') {
              acc[key] = props[key]
            }
            return acc
          }, {}),
          reactUtils: { createElement, useEffect, useRef },
        })
    })
  }
}

export function VueComponentWrapper(options: {
  component: any
  props: Record<string, any>
  reactUtils: typeof ComponentManager.prototype.reactUtils
}) {
  const { component, props, reactUtils } = options
  const { createElement, useEffect, useRef } = reactUtils

  const domRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!domRef.current) return

    const Constructor = Vue.extend(component)

    const instance = new Constructor({
      data: props,
    })
    instance.$mount()

    domRef.current.appendChild(instance.$el)

    return () => {
      instance.$destroy()
    }
  }, [props])

  return createElement('div', { ref: domRef })
}
```

Then register it:

```typescript
import { UniverVue2AdapterPlugin } from './ui-adapter-vue2' // [!code ++]

univer.registerPlugin(UniverUIPlugin)
univer.registerPlugin(UniverVue2AdapterPlugin) // [!code ++]
```

When registering Vue 2.x components, you need to specify the `framework` option as `'vue2'`:

```tsx
const Vue2Component = Vue.component('MyVue2Component', {
  template: '<div>Hello, Univer!</div>',
})

univerAPI.registerComponent('MyVue2Component', Vue2Component, {
  // [!code word:vue2]
  framework: 'vue2',
})
```

### Web Components

If you want to register Web Components, make sure the component adheres to the Web Components standard and install and register the `UniverWebComponentAdapterPlugin` from the `@univerjs/ui-adapter-web-component` package:

#### npm

```bash
npm install @univerjs/ui-adapter-web-component
```

#### pnpm

```bash
pnpm add @univerjs/ui-adapter-web-component
```

#### yarn

```bash
yarn add @univerjs/ui-adapter-web-component
```

#### bun

```bash
bun add @univerjs/ui-adapter-web-component
```

```typescript
import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'

univer.registerPlugin(UniverWebComponentAdapterPlugin)
```

When registering Web Components, you need to specify the `framework` option as `'web-component'`:

```tsx
class WebComponent extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({ mode: 'open' })
    const div = document.createElement('div')
    div.textContent = 'Hello Univer!'
    shadow.appendChild(div)
  }
}

univerAPI.registerComponent('my-web-component', WebComponent, {
  // [!code word:web-component]
  framework: 'web-component',
})
```

### Angular

Convert an Angular component into a custom element with `createCustomElement` from `@angular/elements`, then register it with Univer’s Web Component adapter. This lets an Angular component appear in a Univer sidebar or dialog.

Add `@angular/elements` at the same version as `@angular/core`, and add `@univerjs/ui-adapter-web-component` at the same version as the other Univer packages.

Call this function after initializing Univer. Pass the Angular injector from the host component: obtain it with `private readonly injector = inject(Injector)` and call `registerAngularPanel(univer, univerAPI, this.injector)`. Import `inject` and `Injector` from `@angular/core` in that host component.

```typescript title="angular-panel.ts"
import type { Injector } from '@angular/core'
import type { FUniver, Univer } from '@univerjs/presets'
import { Component } from '@angular/core'
import { createCustomElement } from '@angular/elements'
import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'

@Component({
  selector: 'app-angular-panel',
  standalone: true,
  template: '<p>Hello from Angular!</p>',
})
export class AngularPanelComponent {}

export function registerAngularPanel(univer: Univer, univerAPI: FUniver, injector: Injector) {
  const element = createCustomElement(AngularPanelComponent, { injector })

  univer.registerPlugin(UniverWebComponentAdapterPlugin)
  return univerAPI.registerComponent('univer-angular-panel', element, {
    framework: 'web-component',
  })
}
```

After the editor has rendered, use the registered key as the sidebar content:

```typescript
const sidebar = univerAPI.openSidebar({
  header: { title: 'Angular' },
  children: { label: 'univer-angular-panel' },
  width: 360,
})
```

Keep the returned registration and sidebar handles, and call their `dispose()` methods when they are no longer needed. Register the element once per application: its name must contain a hyphen and differ from the Angular component selector.

Angular Elements maps inputs to element properties and outputs to DOM `CustomEvent` events. Univer’s adapter assigns properties but does not automatically subscribe to Angular outputs; bridge those events explicitly when needed.

## Using Custom Components

Following methods allow you to flexibly integrate various custom components into Univer, enhancing and customizing its functionality.

> [!WARNING: Caution]
> 1. Ensure that Univer has completed rendering before using these methods. 2. For components that need to be
>    registered, ensure they are correctly registered before use. 3. Use the `dispose()` method to clean up and remove
>    added components to avoid memory leaks.

### Adding Custom Menu Items

The top menu bar (Ribbon) and right-click menu (Context Menu) can both include custom components. To add custom components to menu items, create a custom plugin.

### Replacing Built-in Components

> [!ERROR: Warning]
> Replacing built-in components may cause some functionalities to not work properly. Please read the source code and
> documentation thoroughly before attempting to replace components, and proceed with caution.

`univerAPI.registerComponent` registers an override by default (`override: true`). It takes priority over the built-in component regardless of whether the plugin registers its default before or after your call. Only one override can be active for each `componentKey`; dispose the previous registration before registering another. Calling the returned registration’s `dispose()` restores the built-in default, if registered. Use `{ override: false }` to register a default instead.

Register an override before the corresponding view first opens. Registration changes do not automatically rerender already mounted views.

For example, to simply replace the built-in ColorPicker component:

```tsx
// Following code only replaces the UI of the built-in ColorPicker component, not its functionality
univerAPI.registerComponent('UI_COLOR_PICKER_COMPONENT', () => <input type="color" />)
```

### Using as Content Components

#### In the Sidebar

Using the `univerAPI.openSidebar` method, you can open a sidebar in the Univer interface that contains a custom component.

```tsx
// [!code word:MyCustomSidebarComponent]
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent('MyCustomSidebarComponent', () => <div>Hello Univer!</div>)

const sidebar = univerAPI.openSidebar({
  header: { title: 'My Sidebar' },
  children: { label: 'MyCustomSidebarComponent' },
  onClose: () => {
    console.log('close')
  },
  width: 360,
})

// Later close the sidebar
sidebar.dispose()
```

Reference: [`univerAPI.openSidebar`](https://docs.univer.ai/reference/facade/univer.md#opensidebar)

#### In a Dialog

Using the `univerAPI.openDialog` method, you can open a dialog that contains a custom component.

```tsx
// [!code word:MyCustomDialogComponent]
// You should register the component at an appropriate time (e.g., after Univer has loaded)
univerAPI.registerComponent('MyCustomDialogComponent', () => <div>Hello Univer!</div>)

const dialog = univerAPI.openDialog({
  id: 'unique-dialog-id', // The unique identifier for the dialog
  draggable: true,
  width: 300,
  title: { title: 'My Dialog' },
  children: {
    label: 'MyCustomDialogComponent',
  },
  destroyOnClose: true,
  preservePositionOnDestroy: true,
  onClose: () => {},
})

// Later close the dialog
dialog.dispose()
```

Reference: [`univerAPI.openDialog`](https://docs.univer.ai/reference/facade/univer.md#opendialog)
