# How to Use Collapse and Tabs in Element UI for Collapsible Content and Tabbed Navigation

> Learn how to use Element UI Collapse and Tabs for collapsible content and tabbed navigation. Master v-model binding and event emissions with Vue for dynamic UIs.

- Repository: [饿了么前端/element](https://github.com/ElemeFE/element)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Element UI provides `ElCollapse` and `ElTabs` components that use Vue's provide/inject pattern to manage panel states and tab navigation through `v-model` binding and event emissions.**

This guide examines the implementation details of collapsible panels and tabbed interfaces in the [ElemeFE/element](https://github.com/ElemeFE/element) repository. You will learn how to leverage the internal architecture—including the `collapse` and `rootTabs` provide/inject contexts—to build interactive layouts with accordion behavior, editable tabs, and custom slot-based headers.

## Component Architecture and State Management

Both components follow a consistent Vue-style API pattern that separates container logic from child item rendering.

**ElCollapse** manages an array of active panel names (`activeNames`) and provides a `collapse` object to all child items via `provide() { return { collapse: this } }` in [`packages/collapse/src/collapse.vue`](https://github.com/ElemeFE/element/blob/main/packages/collapse/src/collapse.vue). Child `ElCollapseItem` components inject this parent reference to determine their active state and dispatch `item-click` events upward.

**ElTabs** tracks the currently selected pane in `currentName` and provides `rootTabs` to its navigation components and panes through the same provide/inject mechanism defined in [`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue). The root component handles tab clicks, removals, additions, and before-leave validation hooks.

Both containers emit `input` events for `v-model` synchronization and specific change events (`change` for collapse, `tab-click`/`tab-remove` for tabs) to notify parent components of state transitions.

## Implementing Collapsible Panels with ElCollapse

### Basic Multi-Panel Configuration

The standard collapse implementation allows multiple panels to remain open simultaneously. The container stores active names in its `activeNames` data property, which derives from the `value` prop and updates through `setActiveNames()` method calls.

```vue
<template>
  <el-collapse v-model="activeNames" @change="handleChange">
    <el-collapse-item title="Consistency" name="1">
      <p>Design stays consistent across the UI.</p>
    </el-collapse-item>
    <el-collapse-item title="Feedback" name="2">
      <p>Provide clear feedback on user actions.</p>
    </el-collapse-item>
  </el-collapse>
</template>

<script>
export default {
  data() {
    return { activeNames: ['1'] };
  },
  methods: {
    handleChange(val) {
      console.log('Active panels:', val);
    }
  }
};
</script>

```

Each `ElCollapseItem` computes its visibility by checking `this.collapse.activeNames` via the injected parent reference, then uses `ElCollapseTransition` for smooth height animation when opening or closing.

### Accordion Mode for Single Panel Display

Set the `accordion` prop to restrict the active set to a single panel at a time. When enabled, clicking a new panel automatically collapses the previously open one by ensuring `activeNames` contains only the most recently clicked name.

```vue
<template>
  <el-collapse v-model="activeName" accordion>
    <el-collapse-item title="Network Settings" name="network">
      <p>Configure network adapters and proxies.</p>
    </el-collapse-item>
    <el-collapse-item title="Security Settings" name="security">
      <p>Manage firewall and encryption options.</p>
    </el-collapse-item>
  </el-collapse>
</template>

<script>
export default {
  data() {
    return { activeName: 'network' };
  }
};
</script>

```

In [`packages/collapse/src/collapse.vue`](https://github.com/ElemeFE/element/blob/main/packages/collapse/src/collapse.vue), the `handleItemClick` method checks this flag before calling `setActiveNames()` to either replace the entire array (accordion) or toggle the specific name (standard mode).

### Custom Headers with Named Slots

Override the default `title` prop rendering by using the `title` slot (or `#title` in Vue 3 syntax). This allows integration of icons, badges, or complex markup in panel headers.

```vue
<el-collapse-item name="custom">
  <template #title>
    <span>System Status <i class="el-icon-info"></i></span>
    <el-tag size="mini">Updated</el-tag>
  </template>
  <p>Last backup completed successfully.</p>
</el-collapse-item>

```

The slot content renders inside the header trigger element in [`packages/collapse/src/collapse-item.vue`](https://github.com/ElemeFE/element/blob/main/packages/collapse/src/collapse-item.vue), which dispatches the `item-click` event to the parent when clicked.

## Building Tabbed Navigation with ElTabs

### Standard Tab Implementation

The `ElTabs` component manages tab selection through `currentName` and renders navigation headers via the internal `TabNav` component. Bind the `v-model` to synchronize the active tab state.

```vue
<template>
  <el-tabs v-model="activeTab" @tab-click="handleTabClick">
    <el-tab-pane label="User Management" name="users">User content</el-tab-pane>
    <el-tab-pane label="System Logs" name="logs">Log content</el-tab-pane>
  </el-tabs>
</template>

<script>
export default {
  data() {
    return { activeTab: 'users' };
  },
  methods: {
    handleTabClick(tab) {
      console.log('Activated:', tab.name);
    }
  }
};
</script>

```

The `tab-click` event emits when the `handleTabClick` method in [`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue) successfully updates `currentName`, allowing parents to react to navigation changes.

### Editable Tabs with Dynamic Addition and Removal

Enable the `editable` prop to display add and remove controls. The component emits an `edit` event with parameters `(targetName, action)` where action is either `'add'` or `'remove'`.

```vue
<template>
  <el-tabs v-model="editableTab" type="card" editable @edit="handleEdit">
    <el-tab-pane
      v-for="pane in panes"
      :key="pane.name"
      :label="pane.title"
      :name="pane.name">
      {{ pane.content }}
    </el-tab-pane>
  </el-tabs>
</template>

<script>
export default {
  data() {
    return {
      editableTab: '2',
      panes: [
        { title: 'Tab 1', name: '1', content: 'Content 1' },
        { title: 'Tab 2', name: '2', content: 'Content 2' }
      ],
      tabIndex: 2
    };
  },
  methods: {
    handleEdit(target, action) {
      if (action === 'add') {
        const name = String(++this.tabIndex);
        this.panes.push({ 
          title: `Tab ${name}`, 
          name, 
          content: `Content ${name}` 
        });
        this.editableTab = name;
      } else if (action === 'remove') {
        this.panes = this.panes.filter(p => p.name !== target);
        if (this.editableTab === target) {
          this.editableTab = this.panes[0]?.name;
        }
      }
    }
  }
};
</script>

```

The underlying implementation in [`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue) provides `handleTabAdd` and `handleTabRemove` methods that trigger these events while managing the internal tab bar state.

### Custom Labels and Before-Leave Hooks

Use the `label` slot to customize tab headers with HTML content, and implement the `before-leave` prop to prevent navigation until async validation completes.

```vue
<template>
  <el-tabs 
    v-model="activeTab" 
    type="border-card"
    :before-leave="beforeLeave">
    <el-tab-pane name="settings">
      <span slot="label"><i class="el-icon-setting"></i> Settings</span>
      Configuration panel
    </el-tab-pane>
    <el-tab-pane name="danger">
      <span slot="label"><i class="el-icon-warning"></i> Danger Zone</span>
      Destructive actions
    </el-tab-pane>
  </el-tabs>
</template>

<script>
export default {
  data() {
    return { activeTab: 'settings' };
  },
  methods: {
    beforeLeave(activeName, oldActiveName) {
      if (oldActiveName === 'danger' && this.hasUnsavedChanges) {
        return new Promise((resolve, reject) => {
          this.$confirm('Leave without saving?')
            .then(() => resolve())
            .catch(() => reject());
        });
      }
      return true;
    }
  }
};
</script>

```

The `before-leave` function receives the target and current tab names. If it returns `false` or a rejected Promise, `setCurrentName` in [`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue) aborts the navigation and maintains the current selection.

## Key Implementation Files

Understanding these source files helps debug complex interactions and extend component behavior:

- **[`packages/collapse/src/collapse.vue`](https://github.com/ElemeFE/element/blob/main/packages/collapse/src/collapse.vue)** – Container logic including `activeNames` management, `provide()` definition, and `setActiveNames()` method.
- **[`packages/collapse/src/collapse-item.vue`](https://github.com/ElemeFE/element/blob/main/packages/collapse/src/collapse-item.vue)** – Item component that injects the parent `collapse` object, computes `isActive`, and handles header click dispatches.
- **[`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue)** – Core tab controller with `currentName` state, `rootTabs` provision, and event handlers for add/remove/click actions.
- **[`packages/tabs/src/tab-nav.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tab-nav.vue)** – Navigation rendering, scrollable tab handling, and active bar positioning.
- **[`src/transitions/collapse-transition.js`](https://github.com/ElemeFE/element/blob/main/src/transitions/collapse-transition.js)** – JavaScript-based height transition used by both collapse panels and tab content containers.

## Summary

- **ElCollapse** uses `provide/inject` to share `activeNames` state with child items, supporting both multi-panel and accordion modes through the `accordion` boolean prop.
- **ElTabs** manages `currentName` and provides `rootTabs` to children, emitting `tab-click`, `tab-remove`, and `tab-add` events for external state synchronization.
- Both components support `v-model` binding via `value` props and `input` emissions, enabling two-way data binding with parent components.
- Custom content insertion is available through named slots: `title` on collapse items and `label` on tab panes.
- The `before-leave` prop on tabs accepts functions or Promises to implement navigation guards and validation logic.

## Frequently Asked Questions

### How does Element UI determine which collapse panel is active?

Each `ElCollapseItem` component injects the parent `collapse` object and computes its active state by checking if its `name` exists in the parent's `activeNames` array. When a user clicks the panel header, the item dispatches an `item-click` event to the parent, which executes `handleItemClick` and updates `activeNames` via `setActiveNames()`, triggering a reactive re-render.

### Can I prevent a tab from switching until confirmation is received?

Yes. Pass a function or Promise-returning method to the `before-leave` prop on `ElTabs`. The function receives `(newName, oldName)` as parameters. Return `false` or a rejected Promise to cancel the navigation. This is implemented in [`packages/tabs/src/tabs.vue`](https://github.com/ElemeFE/element/blob/main/packages/tabs/src/tabs.vue) within the `setCurrentName` method, which awaits the hook's resolution before updating `currentName`.

### What is the difference between the `change` event on Collapse and the `tab-click` event on Tabs?

The `change` event on `ElCollapse` emits whenever the `activeNames` array updates, providing the new array of active panel names. The `tab-click` event on `ElTabs` fires when a user clicks a tab label, passing the clicked tab instance and event object. Unlike `change`, `tab-click` fires before `before-leave` validation and can be used to trigger side effects regardless of whether the tab actually switches.

### How do I add icons or custom HTML to tab headers?

Use the `label` slot on `ElTabPane` instead of the `label` prop. The slot content replaces the default text header in the tab navigation bar rendered by [`tab-nav.vue`](https://github.com/ElemeFE/element/blob/main/tab-nav.vue), allowing complex markup including icons, badges, or dynamic status indicators while maintaining the component's built-in click handling and active state styling.