Skip to main content

Tabs

Tab containers are a first-class node type in react-mosaic. They are not a convention built on top of splits — they have their own shape in the tree, their own drop targets, and their own close semantics.

The node shape

interface MosaicTabsNode<T> {
type: 'tabs';
tabs: T[];
activeTabIndex: number;
}

A tab node is a container that displays any number of leaf keys as a tab strip. The active tab is rendered as a full panel below the tabs; clicking a tab header switches activeTabIndex.

Tab nodes can appear anywhere a leaf node can — including inside a split, inside another tab's panels? No: tabs can't nest directly. If a leaf is itself a complex layout, model it as a leaf that renders its own mosaic, not as a tab containing a tab.

A live tabbed layout

Live Editor
function TabsExample() {
  return (
    <div className="live-mosaic-frame">
      <Mosaic
        renderTile={(id, path) => (
          <MosaicWindow path={path} title={`Panel ${id}`}>
            <div style={{ padding: 20, fontFamily: 'sans-serif' }}>
              Tab: <strong>{id}</strong>
            </div>
          </MosaicWindow>
        )}
        initialValue={{
          type: 'split',
          direction: 'row',
          children: [
            'sidebar',
            {
              type: 'tabs',
              tabs: ['inbox', 'drafts', 'sent'],
              activeTabIndex: 0,
            },
          ],
        }}
      />
    </div>
  );
}
Result
Loading...

Drag a tab header to split it out into its own panel. Drag a non-tab panel onto a tab strip to add it to the group.

Close semantics: canClose

Mosaic's canClose prop decides, per tab, whether the close button is rendered, visible-but-disabled, or fully enabled. Return one of three strings:

  • 'canClose' — close button is enabled.
  • 'cannotClose' — close button is visible but disabled (hover tooltip typically explains why).
  • 'noClose' — close button is not rendered at all.
const canClose: TabCanCloseFunction<string> = (tabKey, tabs) => {
if (tabKey === 'home') return 'noClose'; // pinned, no X at all
if (tabKey === 'readme') return 'cannotClose'; // protected, X disabled
if (tabs.length <= 1) return 'cannotClose'; // don't allow empty group
return 'canClose';
};

The three states let you model pinned tabs, protected tabs, and the normal case without building your own custom toolbar.

Editable tab titles

renderTabTitle lets you replace the default label with any React node — including an inline editor. The demo at /demo uses this to let users double-click a tab header and rename it in place. The prop receives { tabKey, isActive }, so you can swap out the render based on which tab is focused.

Tab-group toolbars

Tab nodes have their own toolbar, configured via renderTabToolbar. Tab-specific button variants live in the library: AddTabButton, TabSplitButton, TabExpandButton, TabRemoveButton. See the custom toolbars guide for the full shape.