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, so 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 drive 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 above 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 above 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 above 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 above 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 above 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 above 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 above 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 above 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# The textarea pattern: a single corner handle for when the surrounding layout pins one edge and only the opposite corner should grow.
Drag the corner handles="bottom-right"
Edges only# All four edge handles, no corners, so each axis moves independently and the cursor is always single-axis.
Pick an edge handles="top bottom left right"
All eight handles# Every edge and corner. The corners (NW–SE / NE–SW cursors) sit above the edges so they win on 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, so the cursor stays ns-resize.
Drag the bottom edge handles="bottom"
Horizontal only# A horizontal sash, symmetric to the vertical case: only the right edge moves, 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, so 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 ↘