Skip to main content

Overview

The Tabs plugin allows you to create tabbed content sections, perfect for organizing related information in a compact, user-friendly format. Users can switch between tabs to view different content.

Installation

npm install @yoopta/tabs

Basic Usage

import { Tabs } from '@yoopta/tabs';

const plugins = [
  Tabs,
  // ... other plugins
];

<YooptaEditor
  editor={editor}
  plugins={plugins}
/>

Features

  • Multiple Tabs: Create unlimited tabs
  • Active Tab State: Track which tab is currently active
  • Easy Navigation: Switch between tabs effortlessly
  • Keyboard Controls: Add tabs with Enter key
  • Custom Content: Each tab can contain any content

Structure

The Tabs plugin consists of nested elements:
tabs-container (props: activeTabId)
├── tabs-list
│   └── tabs-item-heading (multiple tabs)
└── tabs-item-content (props: referenceId)

Configuration

import { Tabs } from '@yoopta/tabs';

const plugins = [
  Tabs,
];

Options

display
object

Element Props

tabs-container

activeTabId
string | null
ID of the currently active tab

tabs-item-content

referenceId
string | null
ID linking content to its tab heading

Commands

addTabItem

Add a new tab to the tabs container.
import { TabsCommands } from '@yoopta/tabs';

TabsCommands.addTabItem(editor, blockId, { at: selection });

removeTabItem

Remove a specific tab (to be implemented).
TabsCommands.removeTabItem(editor, blockId, tabId);

Keyboard Behavior

  • Enter on Tab Heading: Creates a new tab
  • Enter in Tab Content: Inserts newline within content
  • Backspace: Prevents deletion at content start
  • Cmd/Ctrl+A in Content: Selects entire content

Initial Structure

When created, the plugin initializes with one tab:
{
  'tabs-container': {
    activeTabId: 'generated-id',
  },
  tabs: [
    {
      heading: 'Tab 1',
      content: 'Tab 1 content',
      referenceId: 'generated-id',
    },
  ],
}

Custom Rendering

import { Tabs } from '@yoopta/tabs';
import { useState } from 'react';

const CustomTabs = Tabs.extend({
  elements: {
    'tabs-container': {
      render: (props) => {
        const { activeTabId } = props.element.props;
        
        return (
          <div className="tabs-container" data-active={activeTabId} {...props.attributes}>
            {props.children}
          </div>
        );
      },
    },
    'tabs-list': {
      render: (props) => (
        <div className="tabs-header" role="tablist" {...props.attributes}>
          {props.children}
        </div>
      ),
    },
    'tabs-item-heading': {
      render: (props) => {
        const isActive = checkIfActive(props.element.id);
        
        return (
          <button
            className={`tab-button ${isActive ? 'active' : ''}`}
            role="tab"
            aria-selected={isActive}
            {...props.attributes}
          >
            {props.children}
          </button>
        );
      },
    },
    'tabs-item-content': {
      render: (props) => {
        const { referenceId } = props.element.props;
        const isActive = checkIfActive(referenceId);
        
        return (
          <div
            className="tab-panel"
            role="tabpanel"
            hidden={!isActive}
            {...props.attributes}
          >
            {props.children}
          </div>
        );
      },
    },
  },
});

Styling Examples

Basic CSS

.tabs-container {
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  overflow: hidden;
}

.tabs-header {
  display: flex;
  border-bottom: 2px solid #e5e7eb;
  background: #f9fafb;
}

.tab-button {
  padding: 0.75rem 1.5rem;
  border: none;
  background: transparent;
  cursor: pointer;
  font-weight: 500;
  color: #6b7280;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
}

.tab-button.active {
  color: #3b82f6;
  border-bottom-color: #3b82f6;
}

.tab-panel {
  padding: 1.5rem;
}

.tab-panel[hidden] {
  display: none;
}

With Icons

const IconTabs = Tabs.extend({
  elements: {
    'tabs-item-heading': {
      render: (props) => {
        const icon = getTabIcon(props.element);
        
        return (
          <button className="tab-button" {...props.attributes}>
            <span className="tab-icon">{icon}</span>
            <span className="tab-text">{props.children}</span>
          </button>
        );
      },
    },
  },
});

Use Cases

Documentation

Different programming language examples

Product Features

Showcase different product features

FAQ Sections

Categorized frequently asked questions

Pricing Plans

Compare different pricing tiers

Best Practices

Use concise, descriptive labels for tabs
Keep number of tabs reasonable (3-7) for better UX
Maintain similar content structure across tabs
Ensure tabs are keyboard navigable
Consider mobile layouts with scrollable tabs

Advanced Patterns

With Tab Icons

const IconTabs = Tabs.extend({
  elements: {
    'tabs-item-heading': {
      props: {
        icon: null,
      },
      render: (props) => {
        const { icon } = props.element.props;
        
        return (
          <button {...props.attributes}>
            {icon && <Icon name={icon} />}
            {props.children}
          </button>
        );
      },
    },
  },
});

With Vertical Layout

const VerticalTabs = Tabs.extend({
  elements: {
    'tabs-container': {
      render: (props) => (
        <div className="tabs-vertical" {...props.attributes}>
          {props.children}
        </div>
      ),
    },
  },
});
.tabs-vertical {
  display: grid;
  grid-template-columns: 200px 1fr;
}

.tabs-vertical .tabs-list {
  flex-direction: column;
}

With Lazy Loading

const LazyTabs = Tabs.extend({
  elements: {
    'tabs-item-content': {
      render: (props) => {
        const { referenceId } = props.element.props;
        const isActive = checkIfActive(referenceId);
        const [loaded, setLoaded] = useState(isActive);
        
        useEffect(() => {
          if (isActive && !loaded) {
            setLoaded(true);
          }
        }, [isActive]);
        
        return (
          <div hidden={!isActive} {...props.attributes}>
            {loaded ? props.children : 'Loading...'}
          </div>
        );
      },
    },
  },
});

Accessibility

The Tabs plugin should follow WAI-ARIA tabs pattern:
  • Tab List: role="tablist"
  • Tab: role="tab", aria-selected
  • Tab Panel: role="tabpanel", hidden
const AccessibleTabs = Tabs.extend({
  elements: {
    'tabs-list': {
      render: (props) => (
        <div role="tablist" aria-label="Content tabs" {...props.attributes}>
          {props.children}
        </div>
      ),
    },
    'tabs-item-heading': {
      render: (props) => {
        const isActive = checkIfActive(props.element.id);
        
        return (
          <button
            role="tab"
            aria-selected={isActive}
            aria-controls={`panel-${props.element.id}`}
            id={`tab-${props.element.id}`}
            {...props.attributes}
          >
            {props.children}
          </button>
        );
      },
    },
    'tabs-item-content': {
      render: (props) => {
        const { referenceId } = props.element.props;
        const isActive = checkIfActive(referenceId);
        
        return (
          <div
            role="tabpanel"
            id={`panel-${referenceId}`}
            aria-labelledby={`tab-${referenceId}`}
            hidden={!isActive}
            {...props.attributes}
          >
            {props.children}
          </div>
        );
      },
    },
  },
});