DevExtreme v24.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Overview

The Tabs component allows you to create a tabbed UI to navigate between pages or views. You can create tab items in the items array, or populate tab items from a dataSource.

DevExtreme Accessibility Compliance
DevExtreme component libraries meet a variety of WCAG and Section 508 compliance standards. To assess this demo’s accessibility level, click the Run AXE® Validation button to launch the AXE® web accessibility evaluation tool.
All trademarks or registered trademarks are property of their respective owners. AXE® Terms of Use
The overall accessibility level of your application depends on the Tabs features used.
Backend API
<template> <div class="tabs-demo"> <div class="widget-container"> <div :class="widgetWrapperClasses"> <DxTabs v-for="(dataSource, index) in dataSources" :key="index" :selected-index="0" :width="tabsWidth" :rtl-enabled="rtlEnabled" :data-source="dataSource" :orientation="orientation" :styling-mode="stylingMode" :icon-position="iconPosition" :show-nav-buttons="showNavButtons" :scroll-by-content="scrollByContent" /> </div> </div> <div class="options"> <div class="caption">Options</div> <div class="option"> <span>Orientation</span> <DxSelectBox :items="orientations" :input-attr="{ 'aria-label': 'Orientation' }" v-model:value="orientation" /> </div> <div class="option"> <span>Styling mode</span> <DxSelectBox :items="stylingModes" :input-attr="{ 'aria-label': 'Styling Mode' }" v-model:value="stylingMode" /> </div> <div class="option"> <span>Icon position</span> <DxSelectBox :items="iconPositions" :input-attr="{ 'aria-label': 'Icon Position' }" v-model:value="iconPosition" /> </div> <div class="option"> <DxCheckBox id="show-navigation-buttons" text="Show navigation buttons" v-model:value="showNavButtons" /> </div> <div class="option"> <DxCheckBox text="Scroll content" v-model:value="scrollByContent" /> </div> <div class="option"> <DxCheckBox text="Full width" v-model:value="fullWidth" /> </div> <div class="option"> <DxCheckBox text="Right-to-left mode" v-model:value="rtlEnabled" /> </div> </div> </div> </template> <script setup lang="ts"> import { ref, computed, watch } from 'vue'; import DxSelectBox from 'devextreme-vue/select-box'; import DxCheckBox from 'devextreme-vue/check-box'; import DxTabs from 'devextreme-vue/tabs'; import { orientations, stylingModes, iconPositions, tabsWithText, tabsWithIconAndText, tabsWithIcon, } from './data.ts'; const fullWidth = ref(false); const rtlEnabled = ref(false); const scrollByContent = ref(false); const showNavButtons = ref(false); const shouldRestrictWidth = ref(false); const orientation = ref(orientations[0]); const iconPosition = ref(iconPositions[0]); const stylingMode = ref(stylingModes[1]); const widgetWrapperClasses = computed(() => [ 'widget-wrapper', `widget-wrapper-${orientation.value}`, shouldRestrictWidth.value && 'strict-width', ]); const tabsWidth = computed(() => (fullWidth.value ? '100%' : 'auto')); const dataSources = computed(() => [ tabsWithText, tabsWithIconAndText, tabsWithIcon, ]); const enforceWidthConstraint = () => { shouldRestrictWidth.value = scrollByContent.value || showNavButtons.value; }; watch(showNavButtons, enforceWidthConstraint); watch(scrollByContent, enforceWidthConstraint); </script> <style> .tabs-demo { display: flex; } .strict-width { max-width: 340px; } .dx-theme-generic .strict-width { max-width: 250px; } .widget-container { display: flex; align-items: center; justify-content: center; flex-grow: 1; width: 100%; min-width: 200px; padding: 16px 32px; overflow: clip; } .widget-wrapper { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 80px; width: 100%; } .widget-wrapper-vertical { width: 100%; flex-direction: row; align-items: center; } .options { display: flex; flex-direction: column; flex-shrink: 0; padding: 20px; background-color: rgba(191, 191, 191, 0.15); } .caption { font-weight: 500; font-size: 18px; } #show-navigation-buttons { margin-top: 22px; } .option { margin-top: 20px; } .dx-tabs { max-width: 100%; } .dx-tabs-vertical { height: 216px; } .dx-viewport:not(.dx-theme-generic) .dx-tabs-horizontal { border-block-end: 1px solid rgb(225, 225, 225, 0.4); } .dx-viewport:not(.dx-theme-generic) .dx-tabs-vertical { height: 232px; border-inline-end: 1px solid rgb(225, 225, 225, 0.4); } </style>
window.exports = window.exports || {}; window.config = { transpiler: 'plugin-babel', meta: { '*.vue': { loader: 'vue-loader', }, '*.ts': { loader: 'demo-ts-loader', }, '*.svg': { loader: 'svg-loader', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, }, paths: { 'root:': '../../../../', 'npm:': 'https://unpkg.com/', }, map: { 'vue': 'npm:vue@3.2.47/dist/vue.esm-browser.js', 'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.1/index.js', 'demo-ts-loader': 'root:utils/demo-ts-loader.js', 'svg-loader': 'root:utils/svg-loader.js', 'mitt': 'npm:mitt/dist/mitt.umd.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@24.1.3/cjs', 'devextreme-vue': 'npm:devextreme-vue@24.1.3/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.8/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme-vue': { main: 'index.js', }, 'devextreme': { defaultExtension: 'js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.13/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, }, }; System.config(window.config);
export const tabsWithText = [ { id: 0, text: 'User', }, { id: 1, text: 'Analytics', }, { id: 2, text: 'Clients', }, { id: 3, text: 'Orders', }, { id: 4, text: 'Favorites', }, { id: 5, text: 'Search', }, ]; export const tabsWithIconAndText = [ { id: 0, text: 'User', icon: 'user', }, { id: 1, text: 'Analytics', icon: 'chart', }, { id: 2, text: 'Clients', icon: 'accountbox', }, { id: 3, text: 'Orders', icon: 'ordersbox', }, { id: 4, text: 'Favorites', icon: 'bookmark', }, { id: 5, text: 'Search', icon: 'search', }, ]; export const tabsWithIcon = [ { id: 0, icon: 'user', }, { id: 1, icon: 'chart', }, { id: 2, icon: 'accountbox', }, { id: 3, icon: 'ordersbox', }, { id: 4, icon: 'bookmark', }, { id: 5, icon: 'search', }, ]; export const orientations = ['horizontal', 'vertical']; export const stylingModes = [ 'primary', 'secondary', ]; export const iconPositions = [ 'top', 'start', 'end', 'bottom', ];
import { createApp } from 'vue'; import App from './App.vue'; createApp(App).mount('#app');
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.3/css/dx.light.css" /> <script src="https://unpkg.com/typescript@4.9.5/lib/typescript.js"></script> <script type="module"> import * as vueCompilerSFC from "https://unpkg.com/@vue/compiler-sfc@3.4.16/dist/compiler-sfc.esm-browser.js"; window.vueCompilerSFC = vueCompilerSFC; </script> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.ts"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"> </div> </div> </body> </html>

Customize Tab Contents and Appearance

You can initialize a tab’s contents (text, icons and badges) with values from underlying data objects. This example demonstrates this technique.

Use the drop-down editors on the right to change the component's orientation, styling mode, and icon position.

You can also specify an item template (itemTemplate) to customize tabs.

Configure Overflow Behavior

The Tabs component stretches to fit its container if you do not specify the component's width. When the total tab width exceeds the component’s width, navigation buttons appear. A user can click these buttons to scroll through the tabs. Use the showNavButtons and scrollByContent properties to control this behavior.