Skip to content

Ext JS Tab Panels — Tab Management, Dynamic Tabs, and Tab Customization

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Ext JS Tab Panels. We cover key concepts, practical examples, and best practices to help you master this topic.

Ext JS Tab panel organizes content into tabs, supporting dynamic add and remove, tab reordering, closable tabs, icon styling, tab events, and integration with routers for bookmarkable views.

What You'll Learn

  • Tab panel configuration and layout
  • Dynamic tab creation and removal
  • Tab events and tab change handling
  • Tab customization (icons, closable, reorderable)
  • Tab content Lazy Loading

Why It Matters

Multiview interfaces — settings panels, document editors, dashboard widgets — benefit from tabbed layouts that save screen space while keeping multiple views accessible. Ext JS Tab panel handles tab lifecycle, content management, and layout automatically.

Real-World Use

A document editing application where each open document appears as a tab, tabs show modified indicators, users can reorder tabs by dragging, right-click shows close-all and close-others options, and tab content loads lazily when first selected.

Tab Panel Architecture

flowchart LR
    A[Tab Panel] --> B[Tab Bar]
    A --> C[Tab Content]
    B --> D[Tab 1]
    B --> E[Tab 2]
    B --> F[Tab 3]
    A --> G[Events]
    G --> H[tabchange]
    G --> I[beforetabchange]
    G --> J[tabadd]
    G --> K[tabremove]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Basic Tab Panel

Ext.create('Ext.tab.Panel', {
  title: 'Dashboard',
  width: 700,
  height: 450,
  activeTab: 0,
  items: [{
    title: 'Overview',
    iconCls: 'x-fa fa-dashboard',
    html: '<h2>Dashboard Overview</h2><p>Welcome to the dashboard. Select a tab to view details.</p>'
  }, {
    title: 'Analytics',
    iconCls: 'x-fa fa-bar-chart',
    html: '<p>Analytics data goes here.</p>',
    closable: true
  }, {
    title: 'Settings',
    iconCls: 'x-fa fa-cogs',
    html: '<p>Application settings.</p>',
    disabled: true
  }],
  renderTo: Ext.getBody()
});

Expected output: A tab panel with three tabs. The first tab is active by default. The Analytics tab has a close button. The Settings tab is disabled (grayed out, not clickable).

Dynamic Tab Management

var tabPanel = Ext.create('Ext.tab.Panel', {
  title: 'Document Editor',
  width: 800,
  height: 500,
  activeTab: 0,
  defaults: {
    bodyPadding: 10,
    closable: true
  },
  items: [{
    title: 'Welcome',
    closable: false,
    html: '<p>Open a document from the File menu.</p>'
  }],
  tbar: [{
    text: 'New Tab',
    handler: function() {
      var count = tabPanel.items.length;
      tabPanel.add({
        title: 'Document ' + count,
        html: '<p>Content for document ' + count + '</p>',
        closable: true
      }).show();
    }
  }, {
    text: 'Close Active',
    handler: function() {
      var active = tabPanel.getActiveTab();
      if (active && active.closable) {
        tabPanel.remove(active);
      }
    }
  }, {
    text: 'Close All',
    handler: function() {
      tabPanel.items.each(function(tab) {
        if (tab.closable) {
          tabPanel.remove(tab);
        }
      });
    }
  }],
  listeners: {
    tabchange: function(tabPanel, newTab, oldTab) {
      if (oldTab) {
        console.log('Left tab:', oldTab.title);
      }
      console.log('Activated tab:', newTab.title);
    },
    remove: function(tabPanel, tab) {
      console.log('Closed:', tab.title);
    }
  },
  renderTo: Ext.getBody()
});

Expected output: Clicking "New Tab" adds a closable tab and activates it. "Close Active" removes the current tab. "Close All" removes all closable tabs, keeping the Welcome tab. The tabchange and remove events log to console.

Lazy Loading Tab Content

Ext.create('Ext.tab.Panel', {
  title: 'Lazy Load Tabs',
  width: 650,
  height: 400,
  activeTab: 0,
  items: [{
    title: 'Home',
    html: '<p>Home content loads immediately.</p>'
  }, {
    title: 'Users',
    // Lazy load using loader
    loader: {
      url: '/api/users-tab-content',
      autoLoad: false, // Load on first activate
      renderer: 'html'
    },
    listeners: {
      activate: function(tab) {
        if (!tab.loaded) {
          tab.loader.load();
          tab.loaded = true;
        }
      }
    }
  }, {
    title: 'Reports',
    // Alternative: Ext.create with deferred render
    lazyItems: [{
      xtype: 'grid',
      title: 'Reports Grid',
      // grid config
    }],
    listeners: {
      activate: function(tab) {
        if (!tab.rendered) {
          tab.add(tab.lazyItems);
          tab.rendered = true;
        }
      }
    }
  }],
  renderTo: Ext.getBody()
});

Expected output: The Home tab loads immediately. The Users tab loads content via Ajax only when first activated. The Reports tab creates its grid component only when first visited.

Tab Customization

Ext.create('Ext.tab.Panel', {
  title: 'Custom Tabs',
  width: 700,
  height: 400,
  tabBar: {
    // Tab bar position
    dock: 'top', // 'top', 'bottom', 'left', 'right'
    // Allow reordering
    enableTabScroll: true,
    // Plain style (no background gradient)
    plain: true
  },
  items: [{
    title: 'Email',
    iconCls: 'x-fa fa-envelope',
    closable: true,
    // Tooltip on tab
    tooltip: 'Check your inbox',
    // Tab config customization
    tabConfig: {
      style: 'background: #e8f5e9;'
    },
    html: '<p>Email content</p>'
  }, {
    title: 'Calendar',
    iconCls: 'x-fa fa-calendar',
    closable: true,
    tabConfig: {
      color: 'blue' // Neptune theme supports tab colors
    },
    html: '<p>Calendar view</p>'
  }, {
    title: 'Tasks',
    iconCls: 'x-fa fa-check-square',
    // Badge / notification count
    badgeText: '5',
    html: '<p>Task list</p>'
  }],
  // Tab reordering plugin
  plugins: [{
    ptype: 'tabreorderer'
  }],
  renderTo: Ext.getBody()
});

Expected output: Tabs show icons, closable buttons, tooltips, and badges. Tab bar is at the top with scroll for overflow. Tabs can be dragged to reorder.

Tab Context Menu

var tabPanel = Ext.create('Ext.tab.Panel', {
  title: 'Tab Context Menu',
  width: 700,
  height: 400,
  items: [
    { title: 'Tab 1', html: '<p>Tab 1 content</p>', closable: true },
    { title: 'Tab 2', html: '<p>Tab 2 content</p>', closable: true },
    { title: 'Tab 3', html: '<p>Tab 3 content</p>', closable: true }
  ],
  listeners: {
    // Context menu on tab
    tabcontextmenu: function(tabPanel, tab, event) {
      event.stopEvent();
      var menu = Ext.create('Ext.menu.Menu', {
        items: [{
          text: 'Close Tab',
          handler: function() { tabPanel.remove(tab); }
        }, {
          text: 'Close Others',
          handler: function() {
            tabPanel.items.each(function(item) {
              if (item !== tab && item.closable) {
                tabPanel.remove(item);
              }
            });
          }
        }, {
          text: 'Close All',
          handler: function() {
            tabPanel.items.each(function(item) {
              if (item.closable) { tabPanel.remove(item); }
            });
          }
        }, '-', {
          text: 'Reload',
          handler: function() { console.log('Reload:', tab.title); }
        }]
      });
      menu.showAt(event.getXY());
    }
  },
  renderTo: Ext.getBody()
});

Expected output: Right-clicking a tab opens a context menu with Close Tab, Close Others, Close All, and Reload options.

Tab Events

Ext.create('Ext.tab.Panel', {
  title: 'Tab Events Demo',
  width: 700,
  height: 400,
  items: [
    { title: 'Console', html: '<p>Watch the console for events.</p>' },
    { title: 'Data', html: '<p>Data view</p>' },
    { title: 'Logs', html: '<p>System logs</p>' }
  ],
  listeners: {
    beforetabchange: function(tabPanel, newTab, oldTab) {
      // Prevent switching if condition not met
      if (newTab.title === 'Logs' && !userHasPermission) {
        Ext.Msg.alert('Access Denied', 'You do not have permission to view logs.');
        return false;
      }
    },
    tabchange: function(tabPanel, newTab) {
      // Update URL hash for bookmarking
      window.location.hash = newTab.title.toLowerCase();
    },
    add: function(tabPanel, tab) {
      console.log('Tab added:', tab.title);
    },
    remove: function(tabPanel, tab) {
      console.log('Tab removed:', tab.title);
    },
    enable: function(tab) {
      console.log('Tab enabled:', tab.title);
    },
    disable: function(tab) {
      console.log('Tab disabled:', tab.title);
    }
  },
  renderTo: Ext.getBody()
});

Expected output: Switching tabs fires beforetabchange (can cancel), tabchange (updates URL hash), and add/remove events when tabs are added or removed.

Common Mistakes

  1. Not setting activeTab - Without activeTab, all tabs may appear inactive. Set activeTab to the index or reference of the initial active tab.

  2. Destroying tab content without removing from tab panel - Always call tabPanel.remove(tab) instead of directly destroying the tab's content. The remove method handles cleanup.

  3. Loading all tab content upfront - Each tab renders its content when the tab panel renders. For performance, use deferred rendering with activate listeners for tabs that aren't immediately visible.

  4. Forgetting closable: true on dynamic tabs - New tabs added programmatically with items do not show a close button by default. Set closable: true on each tab or in defaults.

  5. Nesting tab panels inside other tab panels - Nested tabs are visually confusing. Use a single tab panel with panels inside each tab, or use window/managed layouts.

Practice Questions

  1. How do you add a new tab programmatically and make it active?
  2. What event can prevent a tab switch from happening?
  3. How do you lazy load tab content only when first activated?
  4. What is the purpose of the beforetabchange event?
  5. How do you add a context menu to tabs?

Challenge: Build a tabbed document editor with: a welcome tab (not closable), dynamic document tabs created from a new button, lazy loading content from server on first tab activation, tab context menu (close, close others, close all, rename), tab reordering via drag-and-drop, badge showing unsaved changes, and beforetabchange preventing switching if there are unsaved changes.

FAQ

What is the difference between activeTab and setActiveTab()?

activeTab is the config property for initial setup. setActiveTab(tab) is the runtime method to switch tabs. Always use setActiveTab() after initialization.

Can I place the tab bar on the left or right side?

Yes. Set tabBar.dock to 'left', 'right', 'bottom', or 'top' (default). Left/right dock rotates the tab text vertically.

How do I show a loading indicator while tab content loads?

Add a loadmask to the tab: tab.setLoading(true) before the Ajax call, then tab.setLoading(false) in the callback.

Can tabs be reordered by the user?

Yes. Add the tabreorderer plugin to the tab panel: plugins: [{ ptype: 'tabreorderer' }].

How do I prevent a specific tab from being closed?

Leave closable: undefined or false on that tab. In beforeremove or remove events, you can also check the tab's properties and cancel the removal.

Mini Project

Build a tabbed admin panel with: a fixed dashboard tab showing summary cards, dynamic tabs for each section (users, products, orders) loaded on first activation, tab context menu with close options, tab badge showing pending counts, drag-to-reorder tabs, and URL hash synchronization with the active tab for browser back/forward support.

What's Next

Tabs organize content in a single view. Learn how Ext JS Windows and Dialogs create modal dialogs, floating Windows, message boxes, and custom popup panels.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro