# Custom Tabs

> Add your own tabs, groups and buttons to the Document Editor ribbon: its HTML and CSS classes, inserting them at ribbonTabsLoaded and handling their clicks.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Document Editor > Ribbon and UI > Custom Tabs
- **5635 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/custom-tabs/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/custom-tabs/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/custom-tabs/llms-full.txt

---

Functions of your application, such as inserting data or saving to your own storage, can sit in the ribbon of the Document Editor next to the built-in tools. You add tabs, groups and buttons of your own, connect them to the JavaScript API, add a group to a built-in tab and switch to your tab.

The examples change the view of the [ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/aspnet-core/llms-full.txt) tutorial. How the ribbon is built, and how built-in elements are removed, is described in [Ribbon](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/ribbon/llms-full.txt).

### How Custom Tabs Work

The JavaScript API has no methods that create tabs, groups or buttons. The ribbon consists of HTML elements and CSS, so you add your own elements to it with the methods of the browser's DOM:

1. Write the HTML of the tab, group or button with the CSS classes of the ribbon, so that it looks like the built-in elements.
2. Insert the elements when the editor raises `ribbonTabsLoaded`. Before that event, the ribbon isn't complete.
3. Handle the clicks on your buttons and call the JavaScript API.
4. For a new tab, show its content when its header is clicked, and hide it when the user selects another tab.

The element IDs and CSS classes of the ribbon come from the blog articles and samples of Text Control, not from the API reference. In version 34.0, the ribbons of the editor on the Core engine were changed, so test your additions after every upgrade.

### Add a Custom Tab

A tab consists of a header and a content element:

- The **header** is a list item with a link, added to the list of tab headers (`#ribbonbar ul.tabs`). The `rel` attribute of the link contains the ID of the content element.
- The **content** is a `div` element with the class `tab-content`, added to the container of all tab contents (`#txRibbonTabContentContainer`). It contains the groups of the tab.

The following view adds a **Custom** tab with a Document group and two buttons: **Insert Name** inserts a name at the input position, and **Save as HTML** logs the document as HTML. Replace the content of *Views/Home/Index.cshtml*, and put two icons of 32 by 32 pixels as *name.png* and *save.png* into the *wwwroot/images* folder of the project:

```cshtml
@using TXTextControl.Web.MVC

@{
    var sDocument = "<html><body><p>Welcome to <strong>Text Control</strong></p></body></html>";
}

@Html.TXTextControl().TextControl(settings => {
    settings.UserNames = new string[] { "Tim Typer" };
}).LoadText(sDocument, TXTextControl.Web.StringStreamType.HTMLFormat).Render()

<!-- The header of the custom tab -->
<template id="customTabHeader">
    <li>
        <a id="tabCustom" tabindex="-1" rel="ribbonTabCustom" data-applicationmodes="0" data-text="Custom">
            <div class="txTabTextContainer">Custom</div>
        </a>
    </li>
</template>

<!-- The content of the custom tab: one group with two buttons -->
<template id="customTabContent">
    <div class="tab-content" id="ribbonTabCustom" style="display: none;">
        <div class="ribbon-group" id="ribbonGroupCustomDocument" data-applicationmodes="0">
            <div class="ribbon-group-content">
                <div class="ribbon-button ribbon-button-big" id="btnCustomName" role="button">
                    <div class="ribbon-button-big-image-container">
                        <img src="/images/name.png" alt="Insert Name" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                    </div>
                    <div class="ribbon-button-big-label-container">
                        <p class="ribbon-button-label"><span class="ribbon-button-label-text">Insert Name</span></p>
                    </div>
                </div>
                <div class="ribbon-button ribbon-button-big" id="btnCustomSave" role="button">
                    <div class="ribbon-button-big-image-container">
                        <img src="/images/save.png" alt="Save as HTML" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                    </div>
                    <div class="ribbon-button-big-label-container">
                        <p class="ribbon-button-label"><span class="ribbon-button-label-text">Save as HTML</span></p>
                    </div>
                </div>
            </div>
            <div class="ribbon-group-footer">
                <div class="ribbon-group-label-container">
                    <p class="ribbon-group-label">Document</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    // Elements can only be added when the ribbon has been loaded completely
    TXTextControl.addEventListener("ribbonTabsLoaded", function () {
        var tabs = document.querySelector("#ribbonbar ul.tabs");
        var contentContainer = document.getElementById("txRibbonTabContentContainer");

        if (!tabs || !contentContainer) {
            console.error("Ribbon elements not found");
            return;
        }

        // Add the header after the last tab and the content to the content container
        tabs.appendChild(document.getElementById("customTabHeader").content.cloneNode(true));
        contentContainer.appendChild(document.getElementById("customTabContent").content.cloneNode(true));

        document.getElementById("tabCustom").addEventListener("click", activateCustomTab);

        document.getElementById("btnCustomName").addEventListener("click", function () {
            TXTextControl.selection.setText("Tim Typer");
        });

        document.getElementById("btnCustomSave").addEventListener("click", function () {
            TXTextControl.saveDocument(TXTextControl.StreamType.HTMLFormat, function (result) {
                console.log(result.data);
            });
        });
    });

    // Hide all tab contents, show the custom one and mark its header as selected
    function activateCustomTab() {
        TXTextControl.ribbon.expand();

        document.querySelectorAll("#txRibbonTabContentContainer div.tab-content").forEach(function (content) {
            content.style.display = "none";
        });
        // The link and the text container of each header carry the class "selected"
        document.querySelectorAll("#ribbonbar ul.tabs a, #ribbonbar ul.tabs .txTabTextContainer").forEach(function (header) {
            header.classList.remove("selected");
        });

        document.getElementById("ribbonTabCustom").style.display = "inline-block";
        document.getElementById("tabCustom").classList.add("selected");
        document.querySelector("#tabCustom .txTabTextContainer").classList.add("selected");
    }

    // Hide the custom tab when the user selects a built-in tab
    TXTextControl.addEventListener("ribbonTabIndexChanged", function () {
        var content = document.getElementById("ribbonTabCustom");
        if (content) {
            content.style.display = "none";
            document.getElementById("tabCustom").classList.remove("selected");
        }
    });
</script>
```

The code works as follows:

- The HTML of the tab is in `template` elements, which the browser doesn't display. `cloneNode` copies their content when the ribbon is ready.
- The `ribbonTabsLoaded` handler inserts the header into the list of tab headers and the content into `#txRibbonTabContentContainer`. It checks first that both elements exist, because their IDs can change between versions.
- The buttons get click handlers with `addEventListener`. **Insert Name** calls [Selection.setText](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/selection/settext-method/llms-full.txt), which inserts the text at the input position. **Save as HTML** calls [saveDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/savedocument-method/llms-full.txt), whose [SaveDocumentResult.data](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/savedocumentresult/data-property/llms-full.txt) contains the HTML as plain text.
- `activateCustomTab` switches to the custom tab: [Ribbon.expand](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/ribbon/expand-method/llms-full.txt) expands a minimized ribbon, the contents of all other tabs are hidden and the class `selected` marks the header of the custom tab, on its link and on its `txTabTextContainer` element, as the built-in headers have it.
- The `ribbonTabIndexChanged` handler hides the custom tab again when the editor switches to one of its own tabs.

Run the application: the ribbon has a **Custom** tab after its built-in tabs. Click it, place the cursor in the text and click **Insert Name**.

![Ribbon of the Document Editor with the added Custom tab selected, showing the Document group with the buttons Insert Name and Save as HTML](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-server/custom-ribbon-tab.0da260ce.webp)

The markup follows the built-in groups of the Document Editor of `TXTextControl.Web` 34.5.0, in which the large buttons are direct children of `ribbon-group-content`. With an extra wrapper element around each button, the buttons of the group are stacked below each other in that version instead of being placed side by side.

Give every element you add an ID of its own. An ID that a built-in element already uses, such as `ribbonGroupClipboard`, makes both elements hard to address.

#### The CSS Classes of the Ribbon

| Element | CSS classes |
|---|---|
| Tab content | `tab-content` |
| Group | `ribbon-group`, with `ribbon-group-content` for the buttons and `ribbon-group-footer` with `ribbon-group-label-container` and `ribbon-group-label` for its name |
| Large button | `ribbon-button ribbon-button-big`, placed directly in `ribbon-group-content` |
| Button image | `ribbon-button-big-image` in a `ribbon-button-big-image-container` |
| Button label | `ribbon-button-label` with `ribbon-button-label-text`, in a `ribbon-button-big-label-container` |

Give buttons `role="button"` and their images an `alt` text, so that screen readers can describe them. The icons are files of your application; host them in *wwwroot* or another folder your application serves.

### Add a Group to a Built-In Tab

A group can also be added to a built-in tab, next to one of its groups. The following code inserts a group with a **Toggle Bold** button in front of the Font group of the **Home** tab. Add the template and the script to the view above, and put an icon *bold.png* into *wwwroot/images*:

```html
<template id="customHomeGroup">
    <div class="ribbon-group" id="ribbonGroupCustomHome" data-applicationmodes="0">
        <div class="ribbon-group-content">
            <div class="ribbon-button ribbon-button-big" id="btnCustomBold" role="button">
                <div class="ribbon-button-big-image-container">
                    <img src="/images/bold.png" alt="Toggle Bold" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                </div>
                <div class="ribbon-button-big-label-container">
                    <p class="ribbon-button-label"><span class="ribbon-button-label-text">Toggle Bold</span></p>
                </div>
            </div>
        </div>
        <div class="ribbon-group-footer">
            <div class="ribbon-group-label-container">
                <p class="ribbon-group-label">Custom</p>
            </div>
        </div>
    </div>
</template>

<script>
    TXTextControl.addEventListener("ribbonTabsLoaded", function () {
        var fontGroup = document.getElementById("ribbonGroupFont");

        if (!fontGroup) {
            console.error("Font group not found");
            return;
        }

        // Insert the group in front of the Font group of the Home tab
        fontGroup.parentNode.insertBefore(
            document.getElementById("customHomeGroup").content.cloneNode(true), fontGroup);

        document.getElementById("btnCustomBold").addEventListener("click", function () {
            TXTextControl.selection.getBold(function (bold) {
                TXTextControl.selection.setBold(!bold);
            });
        });
    });
</script>
```

- `ribbonGroupFont` is the ID of the Font group. `insertBefore` inserts the new group in front of it, in the same tab.
- [Selection.getBold](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/selection/getbold-method/llms-full.txt) passes whether the selected text is bold, and [setBold](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/selection/setbold-method/llms-full.txt) changes it. [Text and Paragraph Formatting](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/formatting/llms-full.txt) describes the other formatting methods.

To find the IDs of other groups, inspect the ribbon with the developer tools of the browser.

### Add a File Tab

The ribbon of the ASP.NET Core Document Editor has no File menu for opening and saving documents. The blog article [Build a Custom Backstage View in ASP.NET Core with TX Text Control](https://www.textcontrol.com/blog/2026/02/17/build-a-custom-backstage-view-in-aspnet-core-with-tx-text-control/) adds a **File** tab as the first tab. It opens an overlay over the editor in which users choose documents in formats such as DOCX, RTF, TX and PDF, which a controller loads on the server. Its complete source code, which uses `TXTextControl.Web` 34.1.0, is available on GitHub: [TXTextControl.Web.Backstage](https://github.com/TextControl/TXTextControl.Web.Backstage). The functions of such a tab use the methods described in [Loading and Saving Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/load-save/llms-full.txt).

### Custom Tabs in Other Frontends

The HTML and the handlers are the same in every frontend. Only the place where the `ribbonTabsLoaded` handler is attached differs:

- **Angular.** Attach it in a handler of the component's `txDocumentEditorLoaded` event. The blog article [Customizing the Ribbon in TX Text Control for Angular](https://www.textcontrol.com/blog/2024/09/11/customizing-the-ribbon-in-tx-text-control-for-angular/) loads the HTML of a group from a file and inserts it this way.
- **React.** Attach it in the function of the `onLoad` property, as shown in [React](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/react/llms-full.txt).
- **Plain JavaScript page.** Attach it after the call of `TXTextControl.init`, as shown in [JavaScript](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/javascript/llms-full.txt).

### Next Steps

- [Ribbon](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/ribbon/llms-full.txt): select, minimize and remove built-in tabs and groups.
- [Context Menus](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/context-menus/llms-full.txt): add your functions to the context menus.
- [JavaScript API](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/javascript-api/llms-full.txt): the methods your buttons call.

---

## Nearby

- **Previous:** [Context Menus](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/context-menus/llms-full.txt)
- **Next:** [Localization](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/ribbon-ui/localization/llms-full.txt)
