DevExtreme v24.1 is now available.

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

Your search did not match any results.

React Splitter

You can use our Splitter UI widgets to create resizable panes within your DevExtreme-powered web app/page.

To get started with DevExtreme Splitter, refer to the following tutorial for step-by-step instructions: Getting Started with Splitter.

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 Splitter features used.
Backend API
import React from 'react'; import Splitter, { Item } from 'devextreme-react/splitter'; import PaneContent from './PaneContent.tsx'; const PaneContentWithTitle = (title: string, initialSize?: string) => (data) => (<PaneContent title={title} {...data} size={initialSize} />); const App = () => ( <React.Fragment> <Splitter id="splitter" > <Item resizable={true} size="140px" minSize="70px" render={PaneContentWithTitle('Left Pane', '140px')} /> <Item resizable={true} > <Splitter orientation="vertical" > <Item resizable={true} collapsible={true} maxSize="75%" collapsedSize="8%" render={PaneContentWithTitle('Central Pane')} /> <Item resizable={true} collapsible={true} > <Splitter> <Item resizable={true} collapsible={true} size="30%" minSize="5%" render={PaneContentWithTitle('Nested Left Pane', '30%')} /> <Item resizable={true} render={PaneContentWithTitle('Nested Central Pane')} /> <Item resizable={true} collapsible={true} size="30%" minSize="5%" render={PaneContentWithTitle('Nested Right Pane', '30%')} /> </Splitter> </Item> </Splitter> </Item> <Item resizable={false} collapsible={false} size="140px" render={PaneContentWithTitle('Right Pane', '140px')} /> </Splitter> </React.Fragment> ); export default App;
import React from 'react'; import Splitter, { Item } from 'devextreme-react/splitter'; import PaneContent from './PaneContent.js'; const PaneContentWithTitle = (title, initialSize) => (data) => ( <PaneContent title={title} {...data} size={initialSize} /> ); const App = () => ( <React.Fragment> <Splitter id="splitter"> <Item resizable={true} size="140px" minSize="70px" render={PaneContentWithTitle('Left Pane', '140px')} /> <Item resizable={true}> <Splitter orientation="vertical"> <Item resizable={true} collapsible={true} maxSize="75%" collapsedSize="8%" render={PaneContentWithTitle('Central Pane')} /> <Item resizable={true} collapsible={true} > <Splitter> <Item resizable={true} collapsible={true} size="30%" minSize="5%" render={PaneContentWithTitle('Nested Left Pane', '30%')} /> <Item resizable={true} render={PaneContentWithTitle('Nested Central Pane')} /> <Item resizable={true} collapsible={true} size="30%" minSize="5%" render={PaneContentWithTitle('Nested Right Pane', '30%')} /> </Splitter> </Item> </Splitter> </Item> <Item resizable={false} collapsible={false} size="140px" render={PaneContentWithTitle('Right Pane', '140px')} /> </Splitter> </React.Fragment> ); export default App;
import React, { useRef, useEffect } from 'react'; const dimensionOptions = new Set(['size', 'minSize', 'maxSize', 'collapsedSize']); const getPaneState = (data): string => { if (data.resizable !== false && !data.collapsible) { return 'Resizable only'; } const resizableText = data.resizable ? 'Resizable' : 'Non-resizable'; const collapsibleText = data.collapsible ? 'collapsible' : 'non-collapsible'; return `${resizableText} and ${collapsibleText}`; }; const getFilteredDimensionOptions = (data) => Object.entries(data) .filter(([key, value]) => dimensionOptions.has(key) && value) .map(([key, value]) => ({ key, value })); const PaneContent = (data: any) => { const paneContentRef = useRef(null); useEffect(() => { const element = paneContentRef.current.parentNode; element.setAttribute('tabIndex', '0'); }); return ( <div ref={paneContentRef} className="pane-content"> <div className="pane-title">{data.title}</div> <div className="pane-state">{getPaneState(data)}</div> {getFilteredDimensionOptions(data).map((item, index) => ( <div className="pane-option" key={index}> {item.key}: {item.value} </div> ))} </div> ); }; export default PaneContent;
import React from 'react'; import ReactDOM from 'react-dom'; import themes from 'devextreme/ui/themes'; import App from './App.tsx'; themes.initialized(() => { ReactDOM.render( <App />, document.getElementById('app'), ); });
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types@15.8.1/prop-types.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.6/cjs', 'devextreme-react': 'npm:devextreme-react@24.1.6/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.11/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', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins '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': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, '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, react: true, }, }; System.config(window.config);
import React, { useRef, useEffect } from 'react'; const dimensionOptions = new Set(['size', 'minSize', 'maxSize', 'collapsedSize']); const getPaneState = (data) => { if (data.resizable !== false && !data.collapsible) { return 'Resizable only'; } const resizableText = data.resizable ? 'Resizable' : 'Non-resizable'; const collapsibleText = data.collapsible ? 'collapsible' : 'non-collapsible'; return `${resizableText} and ${collapsibleText}`; }; const getFilteredDimensionOptions = (data) => Object.entries(data) .filter(([key, value]) => dimensionOptions.has(key) && value) .map(([key, value]) => ({ key, value })); const PaneContent = (data) => { const paneContentRef = useRef(null); useEffect(() => { const element = paneContentRef.current.parentNode; element.setAttribute('tabIndex', '0'); }); return ( <div ref={paneContentRef} className="pane-content" > <div className="pane-title">{data.title}</div> <div className="pane-state">{getPaneState(data)}</div> {getFilteredDimensionOptions(data).map((item, index) => ( <div className="pane-option" key={index} > {item.key}: {item.value} </div> ))} </div> ); }; export default PaneContent;
import React from 'react'; import ReactDOM from 'react-dom'; import themes from 'devextreme/ui/themes'; import App from './App.js'; themes.initialized(() => { ReactDOM.render(<App />, document.getElementById('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.6/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <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.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.demo-container { height: 500px; } #app { height: 100%; } #splitter { border: 1px solid var(--dx-color-border); border-radius: 8px; overflow: hidden; } .pane-content { padding: 12px; } .pane-title { font-weight: 600; margin-bottom: 2px; } .pane-state { font-size: var(--dx-font-size-xs); margin-bottom: 4px; } .pane-option { color: var(--dx-texteditor-color-label); font-size: 10px; } #splitter > .dx-splitter-item:first-child > .dx-splitter-item-content:focus { border-start-start-radius: 8px; border-end-start-radius: 8px; } #splitter > .dx-splitter-item:last-child > .dx-splitter-item-content:focus { border-end-end-radius: 8px; border-start-end-radius: 8px; } .dx-splitter-item-content:focus { outline: none; box-shadow: inset 0 0 0 1px var(--dx-color-primary); }

To specify individual splitter panes, set the items or dataSource property. Use dataSource if data is remote or should be processed. If you specify multiple panes, they will appear one after another with splitters between them.

The following base options are available for a pane:

  • resizable (default: true)
    If true, a handle appears at the side of the pane. Drag the handle to adjust pane size.

  • collapsible (default: false)
    If true, an arrow appears on the handle. Click the arrow to close the pane.

  • collapsed (default: false)
    Specifies whether the pane is initially collapsed.

To configure the layout of the Splitter component, you can specify its orientation as 'vertical' or 'horizontal' (default). All Splitter "size" properties depend on the orientation. If it is 'horizontal', "size" is the width. Otherwise, it is height. The following pane "size" properties can be specified:

  • size
    Initial pane width/height.

  • minSize
    Minimum width/height for a resizable pane.

  • maxSize
    Maximum width/height for a resizable pane.

  • collapsedSize
    The width/height of a collapsible pane when collapsed.

You can also place one Splitter inside another to create nested layouts. To do so, use the pane's splitter property.

Splitter panes can include different content types, from simple HTML markup to components. You can declare HTML markup inside the item tag or use the following properties to populate panes with content:

  • itemTemplate
    Specifies a custom template for all panes.

  • template
    Specifies a custom template for an individual pane.