Drag-to-resize container with keyboard nudge controls and breakpoint awareness.
Current breakpoint:
px
Focus the drag handle and use arrow keys to resize by keyboard.
Alpine.js Resizable Container
huxResizable controls a container width with pointer and keyboard interactions, tracks the currently matched breakpoint, and can clamp width to min/max or parent bounds.
API
huxResizable(config)
Returns an Alpine data object with:
currentWidth: numberminWidth: numbermaxWidth: number | nullpreventOverflow: booleanstopAtLastBreakpoint: booleaniframeSrc: string | nullbreakpointItems: Array<{ label: string, minWidth: number }>activeBreakpoint: string | nullresolvedMaxWidth: number | nullstartDrag(pointerEvent): voidnudgeWidth(xDelta: number): voidjumpToLabel(breakpointLabel: string): voidjumpToBreakpoint(xDirection: 'next' | 'prev' | 'first' | 'last'): voiddestroy(): void
Internal helper methods are private implementation details and are not part of the supported API contract.
Options
breakpointItems: Array<{ label: string, minWidth: number }>(default: xs/sm/md/lg/xl)minWidth: number(default:375)maxWidth: number | null(default:null)preventOverflow: boolean(default:true)stopAtLastBreakpoint: boolean(default:false)iframeSrc: string | null(default:null)
Quick Start
<div x-data="huxResizable({ iframeSrc: '/examples/preview.html', stopAtLastBreakpoint: true })">
<div
x-ref="resizableContainer"
x-bind:style="`width: ${currentWidth}px; min-width: ${minWidth}px;`"
>
<iframe x-ref="iframeEl" x-bind:src="iframeSrc" title="Preview"></iframe>
<button
type="button"
role="slider"
aria-label="Drag to resize"
aria-orientation="horizontal"
x-bind:aria-valuenow="currentWidth"
x-bind:aria-valuemin="minWidth"
x-bind:aria-valuemax="resolvedMaxWidth"
x-on:mousedown="startDrag($event)"
x-on:touchstart.prevent="startDrag($event)"
x-on:keydown.right.prevent="nudgeWidth(8)"
x-on:keydown.left.prevent="nudgeWidth(-8)"
x-on:keydown.up.prevent="jumpToBreakpoint('next')"
x-on:keydown.down.prevent="jumpToBreakpoint('prev')"
></button>
</div>
</div>Common Usage Patterns
Clamp to Parent Width
huxResizable({
preventOverflow: true,
})Cap at Largest Breakpoint
huxResizable({
stopAtLastBreakpoint: true,
})Custom Breakpoints
huxResizable({
breakpointItems: [
{ label: 'mobile', minWidth: 360 },
{ label: 'tablet', minWidth: 768 },
{ label: 'desktop', minWidth: 1200 },
],
})Dynamic iframe Source
Pass iframeSrc to load a specific URL into the preview iframe. This allows multiple resizable containers on the same page to each load different content without duplicating markup.
huxResizable({
iframeSrc: '/preview/my-component',
})When iframeSrc is null (the default), the iframe renders without a src attribute and displays a blank page.
Behavior Contract
maxWidthlower thanminWidthis normalized up tominWidthduring init.breakpointItemsare filtered to valid shape and sorted byminWidthascending.currentWidthinitializes fromresizableContainer.offsetWidthwhen available.nudgeWidth(),jumpToLabel(), andjumpToBreakpoint()always apply clamp logic before updating width.jumpToBreakpoint()no-ops when direction is invalid or no breakpoints exist.resolvedMaxWidthreturns the effective maximum width (the smallest ofmaxWidth, the last breakpoint whenstopAtLastBreakpointis set, and the parent width whenpreventOverflowis set), clamped to at leastminWidth, ornullwhen the width is unbounded.destroy()removes pointer listeners and disconnects resize observer.
Error Handling
- Invalid breakpoint entries are ignored.
- Missing container refs fall back safely to width defaults; keyboard methods still no-op safely where needed.
- Unknown breakpoint labels in
jumpToLabel()no-op.
Accessibility Notes
- Keep the drag handle as
button type="button"with an explicitaria-label. - Give the drag handle
role="slider"witharia-orientation="horizontal"and bindaria-valuenow/aria-valuemin/aria-valuemaxtocurrentWidth/minWidth/resolvedMaxWidth. Bindaria-valuemaxtoresolvedMaxWidthrather than the rawmaxWidthoption — a slider requiresaria-valuemax, andmaxWidthdefaults tonull, soresolvedMaxWidthderives the effective cap (frommaxWidth, the last breakpoint, or the parent width). - Support keyboard resizing (
ArrowLeft/ArrowRight) and breakpoint jumps (ArrowUp/ArrowDown,Home,End). - Expose current breakpoint changes with
aria-live="polite"status text. - Ensure the active resize affordance has visible focus styles.
- Size the drag handle’s hit area to at least 44×44 CSS px even if the visible bar is thinner — keep the wider hit area inside the container bounds so it isn’t clipped by
overflow-hidden.
Notes
destroy()is part of the supported API here because it performs listener and observer cleanup for long-lived contexts.