Resizable
A generic resize wrapper. Drop it around any React node to gain 8-direction resizing with min/max, aspect-ratio lock, grid snap, keyboard support, and bounds-awareness — all themeable and dark-mode ready.
Basic Usage
Eight handles by default. Drag to resize, or focus a handle with Tab and use arrow keys (Shift for bigger steps).
Basic Resize
Grab any of the 8 handles (4 corners, 4 edges) to resize. Keyboard arrows work too — focus a handle with Tab.
import { Resizable } from 'fluxo-ui';
<Resizable defaultWidth={320} defaultHeight={200} minWidth={120} minHeight={80}>
<div className="p-4">
Resize me using any of the 8 handles.
</div>
</Resizable>Axis & Handle Sets
Lock resizing to a single axis or render only a subset of handles.
Axis & Handle Sets
Restrict resize to one axis or render only a subset of handles.
<Resizable axis="horizontal" defaultWidth={280} defaultHeight={140}>...</Resizable>
<Resizable axis="vertical" defaultWidth={240} defaultHeight={160}>...</Resizable>
<Resizable handles="corners" defaultWidth={240} defaultHeight={160}>...</Resizable>Aspect Ratio & Grid Snap
Pass aspectRatio={16 / 9} for a fixed ratio, or grid={[20, 20]} to snap to a pixel grid.
Aspect Ratio & Grid Snap
Lock to an aspect ratio while resizing, or snap to a pixel grid.
<Resizable aspectRatio={16 / 9} defaultWidth={320} defaultHeight={180}>...</Resizable>
<Resizable grid={[20, 20]} defaultWidth={320} defaultHeight={200}>...</Resizable>Resizable Media
Wrap an <img>, iframe, or anything else. Use showHandles="hover" for a cleaner UX.
Resizable Media
Wrap any element — including images, iframes, or charts. Handles only appear on hover with showHandles='hover'.
<Resizable
defaultWidth={300}
defaultHeight={200}
minWidth={120}
minHeight={80}
showHandles="hover"
ariaLabel="Photo"
>
<img src="/photo.jpg" alt="Mountain view" style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: 8 }} />
</Resizable>Controlled Mode
Pass width + height + onSizeChange for full two-way binding.
Controlled Mode
Provide width and height props (and listen to onSizeChange) for two-way binding with external controls.
const [size, setSize] = useState({ width: 320, height: 200 });
<Resizable
width={size.width}
height={size.height}
onSizeChange={setSize}
minWidth={120}
minHeight={80}
>
...
</Resizable>
<input type="range" min={120} max={500} value={size.width}
onChange={(e) => setSize(s => ({ ...s, width: +e.target.value }))} />Import
import { Resizable } from 'fluxo-ui';
import type { ResizableProps, ResizableSize } from 'fluxo-ui';Resizable Props
childrenreqReact.ReactNodeElement(s) wrapped by the resizable container.
childrenreqReact.ReactNodeElement(s) wrapped by the resizable container.
defaultWidthnumber | stringInitial width in pixels (uncontrolled). Strings are parsed for the leading number.
defaultWidthnumber | stringInitial width in pixels (uncontrolled). Strings are parsed for the leading number.
defaultHeightnumber | stringInitial height in pixels (uncontrolled).
defaultHeightnumber | stringInitial height in pixels (uncontrolled).
widthnumberControlled width in pixels. Provide together with `height` and listen to `onSizeChange`.
widthnumberControlled width in pixels. Provide together with `height` and listen to `onSizeChange`.
heightnumberControlled height in pixels. Provide together with `width` and listen to `onSizeChange`.
heightnumberControlled height in pixels. Provide together with `width` and listen to `onSizeChange`.
onSizeChange(size: { width: number; height: number }) => voidFires on every committed size change (mouse, touch, keyboard).
onSizeChange(size: { width: number; height: number }) => voidFires on every committed size change (mouse, touch, keyboard).
onResizeStart(event: ResizeStartEvent) => voidFires when the user grabs a handle.
onResizeStart(event: ResizeStartEvent) => voidFires when the user grabs a handle.
onResize(event: ResizeChangeEvent) => voidFires continuously while resizing.
onResize(event: ResizeChangeEvent) => voidFires continuously while resizing.
onResizeEnd(event: ResizeEndEvent) => voidFires when the user releases the handle.
onResizeEnd(event: ResizeEndEvent) => voidFires when the user releases the handle.
axis'horizontal' | 'vertical' | 'both'"'both'"Restrict resize direction. Handles incompatible with the axis are hidden.
axis'horizontal' | 'vertical' | 'both'"'both'"Restrict resize direction. Handles incompatible with the axis are hidden.
handlesResizeHandlePosition[] | 'all' | 'corners' | 'edges'"'all'"Which handles to render. `'corners'` = `['ne','nw','se','sw']`, `'edges'` = `['n','s','e','w']`.
handlesResizeHandlePosition[] | 'all' | 'corners' | 'edges'"'all'"Which handles to render. `'corners'` = `['ne','nw','se','sw']`, `'edges'` = `['n','s','e','w']`.
minWidthnumber"40"Minimum width in pixels.
minWidthnumber"40"Minimum width in pixels.
minHeightnumber"40"Minimum height in pixels.
minHeightnumber"40"Minimum height in pixels.
maxWidthnumber"Infinity"Maximum width in pixels.
maxWidthnumber"Infinity"Maximum width in pixels.
maxHeightnumber"Infinity"Maximum height in pixels.
maxHeightnumber"Infinity"Maximum height in pixels.
grid[number, number]Snap size to a [widthStep, heightStep] grid.
grid[number, number]Snap size to a [widthStep, heightStep] grid.
aspectRationumber | booleanLock to a specific aspect ratio (number) or to the starting ratio (true).
aspectRationumber | booleanLock to a specific aspect ratio (number) or to the starting ratio (true).
lockAspectRatioboolean"false"Convenience alias for `aspectRatio = true`.
lockAspectRatioboolean"false"Convenience alias for `aspectRatio = true`.
disabledboolean"false"Disable all resize interaction; handles are hidden.
disabledboolean"false"Disable all resize interaction; handles are hidden.
handleSizenumber"8"Visual / hit area thickness of each handle in pixels.
handleSizenumber"8"Visual / hit area thickness of each handle in pixels.
showHandles'always' | 'hover' | 'never'"'always'"When handles are visible. `'hover'` reveals them on container hover or focus.
showHandles'always' | 'hover' | 'never'"'always'"When handles are visible. `'hover'` reveals them on container hover or focus.
keyboardStepnumber"8"Pixels per arrow-key press on a focused handle.
keyboardStepnumber"8"Pixels per arrow-key press on a focused handle.
keyboardBigStepnumber"32"Pixels per Shift+arrow press on a focused handle.
keyboardBigStepnumber"32"Pixels per Shift+arrow press on a focused handle.
bounds'parent' | 'window' | { width: number; height: number }Optional upper bound for size. Useful inside scroll containers.
bounds'parent' | 'window' | { width: number; height: number }Optional upper bound for size. Useful inside scroll containers.
classNamestringClass name applied to the container.
classNamestringClass name applied to the container.
styleReact.CSSPropertiesInline styles for the container (merged with computed width/height).
styleReact.CSSPropertiesInline styles for the container (merged with computed width/height).
handleClassNamestringClass name applied to every resize handle.
handleClassNamestringClass name applied to every resize handle.
ariaLabelstringUsed as the prefix for each handle's aria-label, e.g. "Widget resize se".
ariaLabelstringUsed as the prefix for each handle's aria-label, e.g. "Widget resize se".
Features
Wraps Any Element
Drop the Resizable wrapper around any React element — images, iframes, charts, divs.
8 Handles or Subset
All edges and corners by default. Pick "edges", "corners", or your own list.
Aspect Ratio Lock
Number for a fixed ratio (16:9, 1:1) or true to keep the starting ratio.
Grid Snap
Provide [stepX, stepY] for perfectly-aligned resize values.
Touch + Keyboard
Mouse, touch, and arrow keys all supported. Shift + arrow for big steps.
Bounds Aware
Constrain to parent, window, or a custom { width, height } bound.
Controlled or Uncontrolled
Use defaultWidth/defaultHeight for autonomous control, or width/height for two-way binding.
Themed Handles
Handles use --eui-* tokens — flip automatically across brand themes and dark mode.