VOOZH about

URL: https://shoelace.style/components/split-panel

⇱ Split Panel


Skip to main content

Split panels display two adjacent panels, allowing the user to reposition them.

<sl-split-panel>
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Examples

Initial Position

To set the initial position, use the position attribute. If no position is provided, it will default to 50% of the available space.

<sl-split-panel position="75">
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>

Initial Position in Pixels

To set the initial position in pixels instead of a percentage, use the position-in-pixels attribute.

<sl-split-panel position-in-pixels="150">
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel position="200"><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Vertical

Add the vertical attribute to render the split panel in a vertical orientation where the start and end panels are stacked. You also need to set a height when using the vertical orientation.

<sl-split-panel vertical style="height: 400px;">
 <div
 slot="start"
 style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel vertical style={{ height: '400px' }}><div
 slot="start"
 style={{
 height: '100%',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '100%',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Snapping

To snap panels at specific positions while dragging, you can use the snap attribute. You can provide one or more space-separated pixel or percentage values, either as single values or within a repeat() expression, which will be repeated along the length of the panel. You can also customize how close the divider must be before snapping with the snap-threshold attribute.

For example, to snap the panel at 100px and 50%, use snap="100px 50%".

<div class="split-panel-snapping">
 <sl-split-panel snap="100px 50%">
 <div
 slot="start"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
 </sl-split-panel>

 <div class="split-panel-snapping-dots"></div>
</div>

<style>
 .split-panel-snapping {
 position: relative;
 }

 .split-panel-snapping-dots::before,
 .split-panel-snapping-dots::after {
 content: '';
 position: absolute;
 bottom: -12px;
 width: 6px;
 height: 6px;
 border-radius: 50%;
 background: var(--sl-color-neutral-400);
 transform: translateX(-3px);
 }

 .split-panel-snapping .split-panel-snapping-dots::before {
 left: 100px;
 }

 .split-panel-snapping .split-panel-snapping-dots::after {
 left: 50%;
 }
</style>

Or, if you want to snap the panel to every 100px interval, as well as at 50% of the panel’s size, you can use snap="repeat(100px) 50%".

<div class="split-panel-snapping-repeat">
 <sl-split-panel snap="repeat(100px) 50%">
 <div
 slot="start"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
 </sl-split-panel>
</div>

<style>
 .split-panel-snapping-repeat {
 position: relative;
 }
</style>

Using a Custom Snap Function

You can also implement a custom snap function which controls the snapping manually. To do this, you need to acquire a reference to the element in Javascript and set the snap property. For example, if you want to snap the divider to either 100px from the left or 100px from the right, you can set the snap property to a function encoding that logic.

panel.snap = ({ pos, size }) => (pos < size / 2 ? 100 : size - 100);

Note that the snap-threshold property will not automatically be applied if snap is set to a function. Instead, the function itself must handle applying the threshold if desired, and is passed a snapThreshold member with its parameters.

<div class="split-panel-snapping-fn">
 <sl-split-panel>
 <div
 slot="start"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
 </sl-split-panel>

 <div class="split-panel-snapping-dots"></div>
</div>

<style>
 .split-panel-snapping-fn {
 position: relative;
 }

 .split-panel-snapping-fn .split-panel-snapping-dots::before,
 .split-panel-snapping-fn .split-panel-snapping-dots::after {
 content: '';
 position: absolute;
 bottom: -12px;
 width: 6px;
 height: 6px;
 border-radius: 50%;
 background: var(--sl-color-neutral-400);
 transform: translateX(-3px);
 }

 .split-panel-snapping-fn .split-panel-snapping-dots::before {
 left: 100px;
 }

 .split-panel-snapping-fn .split-panel-snapping-dots::after {
 left: calc(100% - 100px);
 }
</style>

<script>
 const container = document.querySelector('.split-panel-snapping-fn');
 const splitPanel = container.querySelector('sl-split-panel');
 splitPanel.snap = ({ pos, size }) => (pos < size / 2) ? 100 : (size - 100);
</script>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const css = `
 .split-panel-snapping {
 position: relative;
 }

 .split-panel-snapping-dots::before,
 .split-panel-snapping-dots::after {
 content: '';
 position: absolute;
 bottom: -12px;
 width: 6px;
 height: 6px;
 border-radius: 50%;
 background: var(--sl-color-neutral-400);
 transform: translateX(-3px);
 }

 .split-panel-snapping-dots::before {
 left: 100px;
 }

 .split-panel-snapping-dots::after {
 left: 50%;
 }
`;

const App = () => (
 <><div className="split-panel-snapping"><SlSplitPanel snap="100px 50%"><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel><div className="split-panel-snapping-dots" /></div><style>{css}</style></>
);

Disabled

Add the disabled attribute to prevent the divider from being repositioned.

<sl-split-panel disabled>
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel disabled><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Setting the Primary Panel

By default, both panels will grow or shrink proportionally when the host element is resized. If a primary panel is designated, it will maintain its size and the secondary panel will grow or shrink to fit the remaining space. You can set the primary panel to start or end using the primary attribute.

Try resizing the example below with each option and notice how the panels respond.

<div class="split-panel-primary">
 <sl-split-panel>
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
 </sl-split-panel>

 <sl-select label="Primary Panel" value="" style="max-width: 200px; margin-top: 1rem;">
 <sl-option value="">None</sl-option>
 <sl-option value="start">Start</sl-option>
 <sl-option value="end">End</sl-option>
 </sl-select>
</div>

<script>
 const container = document.querySelector('.split-panel-primary');
 const splitPanel = container.querySelector('sl-split-panel');
 const select = container.querySelector('sl-select');

 select.addEventListener('sl-change', () => (splitPanel.primary = select.value));
</script>
import { useState } from 'react';
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlSelect from '@shoelace-style/shoelace/dist/react/select';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';

const App = () => {
 const [primary, setPrimary] = useState('');

 return (
 <><SlSplitPanel primary={primary}><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel><SlSelect
 label="Primary Panel"
 value={primary}
 style={{ maxWidth: '200px', marginTop: '1rem' }}
 onSlChange={event => setPrimary(event.target.value)}
 ><SlMenuItem value="">None</SlMenuItem><SlMenuItem value="start">Start</SlMenuItem><SlMenuItem value="end">End</SlMenuItem></SlSelect></>
 );
};

Min & Max

To set a minimum or maximum size of the primary panel, use the --min and --max custom properties. Since the secondary panel is flexible, size constraints can only be applied to the primary panel. If no primary panel is designated, these constraints will be applied to the start panel.

This examples demonstrates how you can ensure both panels are at least 150px using --min, --max, and the calc() function.

<sl-split-panel style="--min: 150px; --max: calc(100% - 150px);">
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel style={{ '--min': '150px', '--max': 'calc(100% - 150px)' }}><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Nested Split Panels

Create complex layouts that can be repositioned independently by nesting split panels.

<sl-split-panel>
 <div
 slot="start"
 style="height: 400px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
 >
 Start
 </div>
 <div slot="end">
 <sl-split-panel vertical style="height: 400px;">
 <div
 slot="start"
 style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
 >
 Top
 </div>
 <div
 slot="end"
 style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden"
 >
 Bottom
 </div>
 </sl-split-panel>
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';

const App = () => (
 <SlSplitPanel><div
 slot="start"
 style={{
 height: '400px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div slot="end"><SlSplitPanel vertical style={{ height: '400px' }}><div
 slot="start"
 style={{
 height: '100%',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '100%',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel></div></SlSplitPanel>
);

Customizing the Divider

You can target the divider part to apply CSS properties to the divider. To add a custom handle, slot an icon into the divider slot. When customizing the divider, make sure to think about focus styles for keyboard users.

<sl-split-panel style="--divider-width: 20px;">
 <sl-icon slot="divider" name="grip-vertical"></sl-icon>
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
</sl-split-panel>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';

const App = () => (
 <SlSplitPanel style={{ '--divider-width': '20px' }}><SlIcon slot="divider" name="grip-vertical" /><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel>
);

Here’s a more elaborate example that changes the divider’s color and width and adds a styled handle.

<div class="split-panel-divider">
 <sl-split-panel>
 <sl-icon slot="divider" name="grip-vertical"></sl-icon>
 <div
 slot="start"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 Start
 </div>
 <div
 slot="end"
 style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
 >
 End
 </div>
 </sl-split-panel>
</div>

<style>
 .split-panel-divider sl-split-panel {
 --divider-width: 2px;
 }

 .split-panel-divider sl-split-panel::part(divider) {
 background-color: var(--sl-color-pink-600);
 }

 .split-panel-divider sl-icon {
 position: absolute;
 border-radius: var(--sl-border-radius-small);
 background: var(--sl-color-pink-600);
 color: var(--sl-color-neutral-0);
 padding: 0.5rem 0.125rem;
 }

 .split-panel-divider sl-split-panel::part(divider):focus-visible {
 background-color: var(--sl-color-primary-600);
 }

 .split-panel-divider sl-split-panel:focus-within sl-icon {
 background-color: var(--sl-color-primary-600);
 color: var(--sl-color-neutral-0);
 }
</style>
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
import SlIcon from '@shoelace-style/shoelace/dist/react/icon';

const css = `
 .split-panel-divider sl-split-panel {
 --divider-width: 2px;
 }

 .split-panel-divider sl-split-panel::part(divider) {
 background-color: var(--sl-color-pink-600);
 }

 .split-panel-divider sl-icon {
 position: absolute;
 border-radius: var(--sl-border-radius-small);
 background: var(--sl-color-pink-600);
 color: var(--sl-color-neutral-0);
 padding: .5rem .125rem;
 }

 .split-panel-divider sl-split-panel::part(divider):focus-visible {
 background-color: var(--sl-color-primary-600);
 }

 .split-panel-divider sl-split-panel:focus-within sl-icon {
 background-color: var(--sl-color-primary-600);
 color: var(--sl-color-neutral-0);
 }
`;

const App = () => (
 <><div className="split-panel-divider"><SlSplitPanel><SlIcon slot="divider" name="grip-vertical" /><div
 slot="start"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 Start
 </div><div
 slot="end"
 style={{
 height: '200px',
 background: 'var(--sl-color-neutral-50)',
 display: 'flex',
 alignItems: 'center',
 justifyContent: 'center'
 }}
 >
 End
 </div></SlSplitPanel></div><style>{css}</style></>
);

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

Slots

Name Description
start Content to place in the start panel.
end Content to place in the end panel.
divider The divider. Useful for slotting in a custom icon that renders as a handle.

Learn more about using slots.

Properties

Name Description Reflects Type Default
position The current position of the divider from the primary panel’s edge as a percentage 0–100. Defaults to 50% of the container’s initial size. number 50
positionInPixels
The current position of the divider from the primary panel’s edge in pixels. number -
vertical Draws the split panel in a vertical orientation with the start and end panels stacked. boolean false
disabled Disables resizing. Note that the position may still change as a result of resizing the host element. boolean false
primary If no primary panel is designated, both panels will resize proportionally when the host element is resized. If a primary panel is designated, it will maintain its size and the other panel will grow or shrink as needed when the host element is resized. 'start' | 'end' | undefined -
snap Either one or more space-separated values at which the divider should snap, in pixels, percentages, or repeat expressions e.g. '100px 50% 500px' or repeat(50%) 10px, or a function which takes in a SnapFunctionParams, and returns a position to snap to, e.g. ({ pos }) => Math.round(pos / 8) * 8`. string | SnapFunction -
snapThreshold
How close the divider must be to a snap point until snapping occurs. number 12
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
sl-reposition onSlReposition Emitted when the divider’s position changes. -

Learn more about events.

Custom Properties

Name Description Default
--divider-width The width of the visible divider. 4px
--divider-hit-area The invisible region around the divider where dragging can occur. This is usually wider than the divider to facilitate easier dragging. 12px
--min The minimum allowed size of the primary panel. 0
--max The maximum allowed size of the primary panel. 100%

Learn more about customizing CSS custom properties.

Parts

Name Description
start The start panel.
end The end panel.
panel Targets both the start and end panels.
divider The divider that separates the start and end panels.

Learn more about customizing CSS parts.