# Customizing the Nutrient Web SDK UI

Nutrient Web Viewer provides an API that enables you to customize various components of the SDK’s user interface (UI) directly within the application.

Use cases include:

- Fully replace the default component UI — for example, the comment thread — with your own.

- Insert a custom UI at a predefined [slot](#slots) in an existing component — for example, a custom header in the comment thread.

- Replace an existing [slot](#slots) in a component with your own custom UI — for example, replace the default editor in the comment thread with your own UI.

The support for the new customization API is currently limited to a few components. We’ll be expanding this in the future.

## Slots

The UI customization API is largely enabled through the concept of slots. These are passed to the SDK as part of load configuration using the `ui` property.

```tsx

NutrientViewer.load({
  //... Your configuration
  ui: {
    // Configuration for UI customization.
  },
});

```

You can think of slots as predefined placeholders in the Web SDK UI where you can place your custom UI. The slots themselves might have a default UI, which can be replaced with a custom one, or they might be empty, enabling you to insert your own UI.

For a list of currently supported slots, refer to the [supported slots](https://www.nutrient.io/guides/web/user-interface/ui-customization/supported-slots.md) guide. We’ll gradually expand the slots to cover more components and functionalities.

Consider the following example of the comment thread component:

The comment thread component is a slot that can be fully replaced with a custom UI. In addition to being a slot itself, it also comprises (nested) slots within.

### Concepts

Consider an example where you want to fully replace the entire comment thread UI with your own custom implementation. Specify the `commentThread` slot in the `ui` configuration:

```tsx

NutrientViewer.load({
  //... Your configuration.
  ui: {
    commentThread:...
  }
});

```

To provide a custom UI, a slot accepts a function that returns an object with a `render` method, and the `render` method should return a DOM Node:

```tsx

NutrientViewer.load({
  //... Your configuration.
  ui: {
    commentThread: (instance, id) => {
      return {
        render: (params) => {
          const div = document.createElement("div");
          // Customize the div as needed.
          //...

          // Return a DOM Node.
          return div;
        },
      };
    },
  },
});

```

Here’s the signature of the function, which returns the `render` method:

```ts

type SlotConfigurationCallback = (
  instance,
  id,
) => {
  render?: (params) => HTMLElement | null;
};

```

The SDK internally calls the `SlotConfigurationCallback` function once as it prepares to render the associated component. You can think of it as an initialization phase for the slot. It receives two parameters: `instance` and `id`.

- `instance` is the instance of Nutrient Web SDK, and you may make full use of the SDK APIs available on it.

- `id` is a unique identifier for the specific component being rendered into the slot. A component, such as comment thread, can have multiple instances, and the `id` helps to differentiate between them. `id` is a string value — for example, `01K2HFFCTB5MZKY5H7P7XGYBGG` — which refers to a comment thread ID.

After the `SlotConfigurationCallback` function is called, the SDK stores a reference to the returned object containing the `render` method.

The SDK will internally call `render` any time `params` change and it expects a change in the particular UI. `render` will be called with the updated `params`.

The following are important points to note:

- The returned DOM Node from `render` will be placed into the specified slot.

- If the specified slot is empty, the returned DOM Node will be appended to the slot DOM container.

- If the slot already has a DOM Node, it’ll be replaced with the returned DOM Node from `render`.

- If the slot already has a default UI, it’ll be replaced with the custom UI. For example, the comment thread UI has an `editor` slot which renders the default editor UI. You may replace the UI contained in this slot with your own custom UI.

- If the slot has no default UI, the custom UI will be inserted into the slot. For example, the comment thread has a `header` slot, which is empty by default. You can insert your own custom UI into this slot.

`render` may be called multiple times, so you should treat it as a pure function and avoid side effects inside it. For example, avoid changing any preexisting variables or objects outside its scope.

Use `render` to return a DOM Node that represents the current state of the UI. For example, if `render` is called multiple times with the same `params`, write the logic so that it formulates the same expected markup each time.

The `params` received by `render` contains properties related to the specific slot (such as `id`) that may be useful for constructing the UI. We’ll be adding more useful properties to `params` incrementally.

### Fully customize a slot

Below is an example of how you can fully customize the comment thread UI with your own:

```tsx

NutrientViewer.load({
  //... Your configuration.
  ui: {
    commentThread: (instance, id) => ({
      render: (params) => {
        // Return a DOM Node
        const div = document.createElement("div");
        div.style.backgroundColor = "#FDE1F5";

        div.style.fontSize = "14px";
        div.style.padding = "12px";
        div.style.border = "1px solid #EEC0E1";

        div.style.borderRadius = "16px";
        div.innerText = `This is a custom UI for the comment thread: ${id}`;

        return div;
      },
    }),
  },
});

```

In the example above, you’ll notice the function passed to the `commentThread` slot follows the same `SlotConfigurationCallback` signature as described earlier. This will replace the default comment thread UI with the returned custom UI.

### Nested slots

Slots form a hierarchical structure representing the tree of components available for UI customization. This enables partial customization use cases where you want to replace only a part of the component UI while keeping the rest intact. This is especially useful where you just need to insert a custom UI at a specific location in the component.

To enable nested slots for a particular slot, pass an object instead of a function. To customize a nested slot, pass a function to the nested slot key having the same `SlotConfigurationCallback` signature.

Below is an example of how you can customize the nested slots for `header` and `footer` in a comment thread UI with your own:

```tsx

NutrientViewer.load({
  //... Your configuration.
  ui: {
    // Instead of the `SlotConfigurationCallback` function, we pass an object to the `commentThread` slot specifying nested slots.
    commentThread: {
      // Nested slot for the header having the same `SlotConfigurationCallback` signature.
      header: () => {
        return {
          render: () => {
            const div = document.createElement("div");
            div.style.backgroundColor = "#D4FFDB";

            div.style.padding = "12px";
            div.style.fontSize = "14px";
            div.innerText = "This is a custom header for the comment thread.";

            return div;
          },
        };
      },
      // Nested slot for footer having the same `SlotConfigurationCallback` signature
      footer: () => {
        return {
          render: () => {
            const div = document.createElement("div");
            div.style.backgroundColor = "#FFDDD7";

            div.style.padding = "12px";
            div.style.fontSize = "14px";
            div.innerText = "This is a custom footer for the comment thread.";

            return div;
          },
        };
      },
    },
  },
});

```

The example above inserts the specified custom UI into the header and footer slots respectively, while keeping the rest of the comment thread UI intact.

## Lifecycle methods

In addition to the `render` method, you can also define lifecycle methods in the object returned by the `SlotConfigurationCallback` function. These methods enable you to perform actions at specific points in the component’s lifecycle. They’re also useful for effects such as adding event listeners or performing cleanup.

Additionally, they receive an `id` parameter, which can be used to identify the specific instance of the component.

These are the currently supported lifecycle methods:

- `onMount` — Called when the component is mounted. You can use this to perform any setup actions, such as adding event listeners and analytics events.

- `onUnmount` — Called when the component is unmounted. You can use this to perform any cleanup actions, such as removing event listeners.

The order of execution of all lifecycle methods is as follows:

1. `render` is called for the initial render.

2. `onMount` is called after the component is mounted.

3. `render` may be called multiple times as `params` change.

4. `onUnmount` is called when the component is unmounted.

Below is an example of how you can use lifecycle methods in the `commentThread` slot:

```tsx <!-- Lines inserted: [{range: "13-20"}] -->

NutrientViewer.load({
  //... Your configuration.
  ui: {
    commentThread: (instance, id) => {
      const div = document.createElement("div");

      return {
        render: (params) => {
          // Return a DOM Node.
          div.innerText = `This is a custom UI for the comment thread`;
          return div;
        },
        onMount: (id) => {
          console.log(`Comment thread mounted with id: ${id}`);
          // You can add event listeners or perform other setup actions here.
        },
        onUnmount: (id) => {
          console.log(`Comment thread unmounted with id: ${id}`);
          // You can remove event listeners or perform other cleanup actions here.
        },
      };
    },
  },
});

```

Similarly, you can use lifecycle methods in nested slots. Below is an example of how you can use lifecycle methods in the `header` slot of the `commentThread`:

```tsx <!-- Lines inserted: [{range: "11-18"}] -->

NutrientViewer.load({
  //... Your configuration.
  ui: {
    commentThread: {
      header: (instance, id) => {
        const div = document.createElement("div");
        div.innerText = "This is a custom header for the comment thread.";

        return {
          render: () => div,
          onMount: (id) => {
            console.log(`Header mounted with id: ${id}`);
            // You can add event listeners or perform other setup actions here.
          },
          onUnmount: (id) => {
            console.log(`Header unmounted with id: ${id}`);
            // You can remove event listeners or perform other cleanup actions here.
          },
        };
      },
    },
  },
});

```

## Conclusion

The UI customization API provides ways to fully or partially customize various parts of the Nutrient Web SDK UI with slots. Currently the supported slots are limited, but we’ll be expanding on them in the near future.

For a list of currently supported slots, refer to the [supported slots](https://www.nutrient.io/guides/web/user-interface/ui-customization/supported-slots.md) guide.
---

## Related pages

- [Custom sidebars](/guides/web/user-interface/ui-customization/custom-sidebars.md)
- [Supported slots for UI customization](/guides/web/user-interface/ui-customization/supported-slots.md)
- [Building a comment thread UI with the customization API](/guides/web/user-interface/ui-customization/comment-thread-example.md)
- [Set UI customization configuration](/guides/web/user-interface/ui-customization/set-ui.md)

