Resizable# Component <neo-resizable> is a wrapper that turns any content into a draggable box. The handles attribute picks which edges and corners get a drag handle; clamp the result with min-/max-width and min-/max-height, and snap to a pixel grid with step-horizontal and step-vertical. After the first drag the host owns size as inline styles. Double-click or double-tap a handle to reset its axis back to the natural size.
The preview routes through the in-browser server simulator. Its current Settings (network latency, a non-200 response, or an unreachable server) are in effect. The live preview can look like it is misbehaving. Adjust them in the Settings panel.
Drag the corner
width is signal-editable
HTML CSS
The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.
Signals
Datastar signals declared in the selected state's markup. Change a value to update the preview, applied live while Sync is on, or when you click Patch.
<neo-resizable
data-signals="{resizable_width: '22rem'}"
class="resizable-demo"
data-attr:width="$resizable_width"
handles="bottom-right"
height="11rem"
min-width="10rem"
min-height="6rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag the corner</strong>
<span>width is signal-editable</span>
</neo-layout>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
/* Content styling; the .resizable-demo flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
<neo-resizable
handles="bottom-right"
class="resizable-demo"
height="11rem"
min-height="6rem"
min-width="10rem"
style="width:22rem;--neo-resizable-width:22rem;height:11rem;--neo-resizable-height:11rem;min-width:10rem;min-height:6rem;"
width="22rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag the corner</strong>
<span>handles="bottom-right"</span>
</neo-layout>
<span data-neo-resizable-handle="bottom-right">
<neo-icon name="move-diagonal-2" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
<neo-resizable
handles="top bottom left right"
class="resizable-demo"
height="11rem"
min-height="6rem"
min-width="12rem"
style="width:22rem;--neo-resizable-width:22rem;height:11rem;--neo-resizable-height:11rem;min-width:12rem;min-height:6rem;"
width="22rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Pick an edge</strong>
<span>handles="top bottom left right"</span>
</neo-layout>
<span data-neo-resizable-handle="top">
<neo-icon name="grip-horizontal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="bottom">
<neo-icon name="grip-horizontal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="left">
<neo-icon name="grip-vertical" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="right">
<neo-icon name="grip-vertical" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
<neo-resizable
handles="top bottom left right top-left top-right bottom-left bottom-right"
class="resizable-demo"
height="12rem"
min-height="7rem"
min-width="14rem"
style="width:24rem;--neo-resizable-width:24rem;height:12rem;--neo-resizable-height:12rem;min-width:14rem;min-height:7rem;"
width="24rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag any edge or corner</strong>
<span>full handle set</span>
</neo-layout>
<span data-neo-resizable-handle="top">
<neo-icon name="grip-horizontal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="bottom">
<neo-icon name="grip-horizontal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="left">
<neo-icon name="grip-vertical" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="right">
<neo-icon name="grip-vertical" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="top-left">
<neo-icon name="move-diagonal-2" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="top-right">
<neo-icon name="move-diagonal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="bottom-left">
<neo-icon name="move-diagonal" aria-hidden="true"></neo-icon>
</span>
<span data-neo-resizable-handle="bottom-right">
<neo-icon name="move-diagonal-2" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
<neo-resizable
handles="bottom"
class="resizable-demo"
height="9rem"
max-height="20rem"
min-height="5rem"
style="width:22rem;--neo-resizable-width:22rem;height:9rem;--neo-resizable-height:9rem;min-height:5rem;max-height:20rem;"
width="22rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag the bottom edge</strong>
<span>handles="bottom"</span>
</neo-layout>
<span data-neo-resizable-handle="bottom">
<neo-icon name="grip-horizontal" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
<neo-resizable
handles="right"
class="resizable-demo"
height="9rem"
max-width="36rem"
min-width="10rem"
style="width:18rem;--neo-resizable-width:18rem;height:9rem;--neo-resizable-height:9rem;min-width:10rem;max-width:36rem;"
width="18rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag the right edge</strong>
<span>handles="right"</span>
</neo-layout>
<span data-neo-resizable-handle="right">
<neo-icon name="grip-vertical" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
<neo-resizable
handles="bottom-right"
class="resizable-step"
height="144px"
min-height="72px"
min-width="96px"
step-horizontal="24"
step-vertical="24"
style="width:240px;--neo-resizable-width:240px;height:144px;--neo-resizable-height:144px;min-width:96px;min-height:72px;"
width="240px"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Drag the corner</strong>
<span>step-horizontal="24" step-vertical="24"</span>
</neo-layout>
<span data-neo-resizable-handle="bottom-right">
<neo-icon name="move-diagonal-2" aria-hidden="true"></neo-icon>
</span>
</neo-resizable> .resizable-step {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
box-sizing: border-box;
background-color: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
background-image:
linear-gradient(to right, color-mix(in srgb, var(--accent) 22%, transparent) 1px, transparent 1px),
linear-gradient(to bottom, color-mix(in srgb, var(--accent) 22%, transparent) 1px, transparent 1px);
background-size: 24px 24px;
}
.resizable-step strong { color: var(--page-fg); font-weight: 600; }
.resizable-step span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
/* Content styling; the .resizable-* flex container centers it. */
.resizable-step > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
<neo-resizable
handles="bottom-right"
class="resizable-demo"
height="11rem"
min-height="6rem"
min-width="10rem"
style="width:22rem;--neo-resizable-width:22rem;height:11rem;--neo-resizable-height:11rem;min-width:10rem;min-height:6rem;"
width="22rem"
>
<neo-layout column align-items="center" justify-content="center" gap="xs">
<strong>Custom corner glyph</strong>
<span>data-neo-resizable-icon override</span>
</neo-layout>
<span data-neo-resizable-handle="bottom-right">
<span aria-hidden="true" data-neo-resizable-icon="bottom-right">↘</span>
</span>
</neo-resizable> .resizable-demo {
display: flex;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--btn-border));
border-radius: var(--page-radius, 0.5rem);
background: color-mix(in srgb, var(--accent) 6%, var(--btn-bg));
box-sizing: border-box;
}
.resizable-demo strong { color: var(--page-fg); font-weight: 600; }
.resizable-demo span {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
padding: 0.1em 0.4em;
border-radius: 0.25rem;
background: rgba(127, 127, 127, 0.18);
}
.resizable-demo [data-neo-resizable-icon] { font-size: 1rem; line-height: 1; }
/* Content styling; the .resizable-* flex container centers it. */
.resizable-demo > neo-layout {
padding: 1rem;
box-sizing: border-box;
color: var(--muted);
font-size: 0.9rem;
text-align: center;
pointer-events: none;
}
Reference# Attributes # Name Type Default Description handlesstring observednone Space-separated list: top, bottom, left, right, top-left, top-right, bottom-left, bottom-right. min-widthstring observednone CSS length, mirrored to the inline min-width style. Omit for no minimum (none is invalid for min-*). max-widthstring observednone CSS length or none (no maximum), mirrored to the inline max-width style. min-heightstring observednone CSS length, mirrored to the inline min-height style. Omit for no minimum (none is invalid for min-*). max-heightstring observednone CSS length or none (no maximum), mirrored to the inline max-height style. widthstring observednone Initial width. heightstring observednone Initial height. step-horizontalnumbernone Snap width to a multiple of this many pixels while dragging. Omit for free resize. step-verticalnumbernone Snap height to a multiple of this many pixels while dragging. Omit for free resize.
States # Name Description :state(resizing)Set while a handle drag is in flight. :state(handle-<dir>)Set alongside resizing for the handle being dragged (handle-top, handle-bottom-left, ...); the kit uses it to pick the resize cursor.
Slots # Name Description default Resizable content. [data-neo-resizable-icon]Custom glyph per direction. The attribute value names the handle (e.g. bottom-right).
Events # Name Detail Bubbles Description neo-resizable-startnone yes Fires when a drag begins. neo-resizable-resize{ width: number, height: number }yes Fires on every frame during a drag. neo-resizable-endnone yes Fires when the drag ends.
CSS variables # Name Default Description --neo-resizable-handle-colorvar(--muted, currentColor)Handle color at rest. --neo-resizable-handle-hover-colorvar(--page-fg, currentColor)Handle color on hover or during a drag. --neo-resizable-handle-opacity0.55Handle opacity at rest. --neo-resizable-handle-hover-opacity1Handle opacity on hover or during a drag. --neo-resizable-handle-hover-bgcolor-mix(in srgb, var(--page-fg) 5%, transparent)Handle background on hover or during a drag. --neo-resizable-edge-thickness0.5remHit-area thickness of the edge handles. --neo-resizable-corner-size1remHit-area size of the corner handles. --neo-resizable-icon-size0.85remHandle glyph size.
Examples# Bottom-right corner# A single corner handle, as on a textarea, for when the surrounding layout pins 1 edge and only the opposite corner should grow.
Drag the corner handles="bottom-right"
Edges only# All 4 edge handles and no corners. Each axis moves independently and the cursor is always single-axis.
Pick an edge handles="top bottom left right"
All 8 handles# Every edge and corner. The corners (NW-SE / NE-SW cursors) sit above the edges and take precedence where they overlap.
Drag any edge or corner full handle set
Vertical only# A vertical sash. The layout fixes the host's width; only the bottom edge moves, which keeps the cursor at ns-resize.
Drag the bottom edge handles="bottom"
Horizontal only# A horizontal sash, symmetric to the vertical case. Only the right edge moves and the cursor stays ew-resize.
Drag the right edge handles="right"
Step grid# step-horizontal and step-vertical snap each axis to a multiple of their pixel value while dragging. The box resizes one grid cell at a time instead of continuously. Drag the corner over the grid to see it jump.
Drag the corner step-horizontal="24" step-vertical="24"
Custom handle icon# Drop a child marked data-neo-resizable-icon="<dir>" and the host reuses it instead of injecting the default grip. Only the glyph changes; the handle wrapper, positioning, and cursor stay host-owned.
Custom corner glyph data-neo-resizable-icon override ↘