Split Panel
<sl-split-panel> | SlSplitPanel
Split panels display two adjacent panels, allowing the user to reposition them.
Examples
Basic Split Panel
<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-split-panel div slot="start" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/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> );
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>
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;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
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>
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;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/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>
sl-split-panel vertical="true" style="height: 400px;" div slot="start" style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/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, add the snap
attribute with one or more
space-separated values. Values must be in pixels or percentages. For example, to snap the panel at
100px
and 50%
, use snap="100px 50%"
. You can also customize how close
the divider must be before snapping with the snap-threshold
attribute.
<div class="split-panel-snapping"> <sl-split-panel snap="100px 50%"> <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 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-dots::before { left: 100px; } .split-panel-snapping-dots::after { left: 50%; } </style>
div.split-panel-snapping sl-split-panel snap="100px 50%" div slot="start" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End div.split-panel-snapping-dots 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%; }
import SlSplitPanel from '@teamshares/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>
sl-split-panel disabled="true" div slot="start" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/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>
div.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;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End sl-select label="Primary Panel" value="" style="max-width: 200px; margin-top: 1rem;" sl-option value="" None sl-option value="start" Start sl-option value="end" End javascript: 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));
import { useState } from 'react'; import SlSplitPanel from '@teamshares/shoelace/dist/react/split-panel'; import SlSelect from '@teamshares/shoelace/dist/react/select'; import SlMenuItem from '@teamshares/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>
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;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/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>
sl-split-panel div slot="start" style="height: 400px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" sl-split-panel vertical="true" style="height: 400px;" div slot="start" style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Top div slot="end" style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Bottom
import SlSplitPanel from '@teamshares/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" library="bootstrap"></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>
sl-split-panel style="--divider-width: 20px;" sl-icon slot="divider" name="grip-vertical" library="bootstrap" div slot="start" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End
import SlSplitPanel from '@teamshares/shoelace/dist/react/split-panel'; import SlIcon from '@teamshares/shoelace/dist/react/icon'; const App = () => ( <SlSplitPanel style={{ '--divider-width': '20px' }}> <SlIcon slot="divider" name="grip-vertical" library="bootstrap" /> <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" library="bootstrap"></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>
div.split-panel-divider sl-split-panel sl-icon slot="divider" name="grip-vertical" library="bootstrap" div slot="start" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | Start div slot="end" style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center;" | End 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: 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); }
import SlSplitPanel from '@teamshares/shoelace/dist/react/split-panel'; import SlIcon from '@teamshares/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" library="bootstrap" /> <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.
To import this component from the CDN using a script tag:
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.1.0/cdn/components/split-panel/split-panel.js"></script>
To import this component from the CDN using a JavaScript import:
import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.1.0/cdn/components/split-panel/split-panel.js';
To import this component using a bundler:
import '@shoelace-style/shoelace/dist/components/split-panel/split-panel.js';
To import this component as a React component:
import SlSplitPanel from '@shoelace-style/shoelace/dist/react/split-panel';
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
Scroll right to see the entire table
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
position-in-pixels
|
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
|
One or more space-separated values at which the divider should snap. Values can be in pixels or
percentages, e.g. "100px 50%" .
|
string | undefined
|
- | |
snapThreshold
snap-threshold
|
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.