Skip to main content
Since Shoelace 2.0 Code stable Pattern Tentative Figma Needed

Split Panel

sl-split-panel

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

Examples

Basic Split Panel

Start
End
<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

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.

Start
End
<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.

Start
End
<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

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.

Start
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;"
  >
    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

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.

Start
End
<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%;
  }

Disabled

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

Start
End
<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

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.

Start
End
None Start End
<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));

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.

Start
End
<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

Nested Split Panels

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

Start
Top
Bottom
<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

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.

Start
End
<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

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

Start
End
<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);
  }

Component Props

Property Default Details
position 50

number

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.

positionInPixels
position-in-pixels

number

The current position of the divider from the primary panel’s edge in pixels.

vertical false

boolean

Draws the split panel in a vertical orientation with the start and end panels stacked.

disabled false

boolean

Disables resizing. Note that the position may still change as a result of resizing the host element.

primary

'start' | 'end' | undefined

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.

snap

string | undefined

One or more space-separated values at which the divider should snap. Values can be in pixels or percentages, e.g. "100px 50%".

snapThreshold
snap-threshold
12

number

How close the divider must be to a snap point until snapping occurs.

updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Slots

Name Details
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.

Events

Name Name Name React Event Details
sl-reposition sl-reposition sl-reposition onSlReposition

Emitted when the divider’s position changes.

Learn more about events.

Custom Properties

Name Details
--divider-width

4px

The width of the visible divider.

--divider-hit-area

12px

The invisible region around the divider where dragging can occur. This is usually wider than the divider to facilitate easier dragging.

--min

0

The minimum allowed size of the primary panel.

--max

100%

The maximum allowed size of the primary panel.

Learn more about customizing CSS custom properties.

CSS 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.