Separator
stimeo--separator
APG separator semantics, with an optional focusable value-bearing variant.
The stimeo--separator controller adds role="separator" and aria-orientation for a decorative divider. For the optional focusable, value-bearing variant it keeps aria-valuenow in sync and emits stimeo--separator:change on arrow-key adjustment (ArrowUp/ArrowRight increase, ArrowDown/ArrowLeft decrease, Home/End jump to the bounds), scoped to the orientation's axis. The actual pane resize/drag is out of scope — use Resizable for that. Behavior only — line drawing is owned by this Playground.
The thin line below is a decorative separator (role="separator" semantics only — nothing to operate).
The handle below is a focusable, value-bearing separator: Tab to it and use the arrow keys to change the split (for mouse-drag resizing, use the Resizable component instead).
Keyboard
| Key | Action |
|---|---|
| Arrow keys | Adjust the value along the orientation axis (focusable variant). |
| Home / End | Jump to the minimum / maximum (focusable variant). |
<%# Markup for the separator demo.
The top is a decorative separator (only role="separator" and aria-orientation).
The bottom is a valued, focusable separator that increments/decrements aria-valuenow
with the arrow keys and fires stimeo--separator:change. The actual pane-width change
(demo.js) and line rendering (demo.css) are the consumer's job. %>
<div class="separator-demo">
<p class="separator-demo__text"><%= t("components.separator.demo.intro") %></p>
<div
class="separator separator--horizontal"
role="separator"
data-controller="stimeo--separator"
data-stimeo--separator-orientation-value="horizontal"></div>
<p class="separator-demo__text"><%= t("components.separator.demo.after") %></p>
<div class="separator-demo__split">
<div class="separator-demo__pane"><%= t("components.separator.demo.left") %></div>
<div
class="separator separator--vertical"
role="separator"
tabindex="0"
aria-label="<%= t('components.separator.demo.handle') %>"
aria-orientation="vertical"
aria-valuemin="20"
aria-valuemax="80"
aria-valuenow="50"
data-controller="stimeo--separator"
data-stimeo--separator-focusable-value="true"
data-stimeo--separator-step-value="5"
data-action="keydown->stimeo--separator#onKeydown"></div>
<div class="separator-demo__pane"><%= t("components.separator.demo.right") %></div>
</div>
</div>
/*
* Presentation-only styles for the separator demo.
* The line's rendering, color, and thickness are owned by the consumer's CSS. The
* library only gives meaning to role / aria-orientation / aria-valuenow.
*/
.separator-demo {
max-width: 32rem;
}
.separator-demo__text {
margin: 0.5rem 0;
}
.separator--horizontal {
height: 1px;
margin: 0.75rem 0;
background: var(--border-strong);
}
.separator-demo__split {
display: flex;
align-items: stretch;
height: 6rem;
margin-top: 1rem;
border: 1px solid var(--border-default);
border-radius: 0.5rem;
overflow: hidden;
/* demo.js updates this variable from aria-valuenow (%) to set the left pane's width. */
--separator-split: 50%;
}
.separator-demo__pane {
display: grid;
place-items: center;
padding: 0.5rem;
background: var(--surface-subtle);
}
.separator-demo__pane:first-child {
flex: 0 0 var(--separator-split);
}
.separator-demo__pane:last-child {
flex: 1;
}
.separator--vertical {
flex: 0 0 0.5rem;
background: var(--border-strong);
cursor: col-resize;
}
/* Use :focus (not :focus-visible) so the color inversion shows on click too, not
only on keyboard focus — a resize handle is dragged with the mouse, and the user
needs to see it took focus. (Same reasoning as the time_picker demo.) */
.separator--vertical:focus {
outline: 2px solid var(--accent);
outline-offset: -2px;
background: var(--accent);
}
// Consumer-side JS for the separator demo (optional).
// A valued, focusable separator increments/decrements aria-valuenow with the arrow
// keys and fires stimeo--separator:change with { value }.
// Here we reflect that value (%) into the left pane's width to visualize the resize.
// The actual layout/size computation is the consumer's job (the library only gives the
// value meaning).
document.addEventListener("stimeo--separator:change", (event) => {
const split = event.target.closest(".separator-demo__split");
if (!split) return;
split.style.setProperty("--separator-split", `${event.detail.value}%`);
});
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--separator"
Values
| Name | Description | Attribute |
|---|---|---|
orientation
|
Seeds aria-orientation when unset (horizontal default or vertical). |
data-stimeo--separator-orientation-value |
focusable
|
When true, makes the separator a value-bearing, focusable control with arrow-key adjustment (default false). | data-stimeo--separator-focusable-value |
step
|
The amount aria-valuenow changes per arrow-key press (default 1). |
data-stimeo--separator-step-value |
Actions
| Name | Description | Action |
|---|---|---|
onKeydown
|
Adjusts aria-valuenow via arrow keys and Home/End on the focusable variant, clamped to the range. |
stimeo--separator#onKeydown |
Events
| Name | Description | Event |
|---|---|---|
change
|
Dispatched when an arrow/Home/End key changes the value, with the clamped { value } in detail. |
stimeo--separator:change |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
aria-orientation |
Root element | "horizontal" / "vertical". |
aria-valuenow |
Root element | The current value of a value-bearing separator (focusable). |