# useDraggableElement

*Category:* Utilities
*Section:* DOM behavior hooks
*Source:* https://uikit.developers.rio.cloud/#/utilities/useDraggableElement
*Captured:* 2026-04-27T14:59:34.226Z

The useDraggableElement hook provides drag behavior for an element. It manages pointer events, viewport clamping and draggable position state for UI elements like floating panels or overlays. Viewport mode renders through the shared portal root, while container mode is intended for absolutely positioned elements that must stay aligned with a scrollable boundary. The hook can also apply an initial position, including centered placement. The renderElement helper is mainly relevant for viewport mode; in container mode it simply returns the element unchanged.

## useDraggableElement

### Example: Example 1

Use the drag icon to move the fixed panel around the viewport. Viewport mode automatically renders through the shared portal root.
Show draggable element Reset panel position

#### React (tsx)

```tsx
import { useState, type CSSProperties } from 'react';

import Button from '@rio-cloud/rio-uikit/Button';
import useDraggableElement from '@rio-cloud/rio-uikit/hooks/useDraggableElement';
import ButtonToolbar from '@rio-cloud/rio-uikit/ButtonToolbar';
import classNames from '@rio-cloud/rio-uikit/classNames';

const defaultPositionStyle: CSSProperties = {
    top: '180px',
    left: '50%',
    transform: 'translateX(-50%)',
};

const DraggablePanel = () => {
    const { draggableRef, isDragging, position, onDragHandleDown, renderElement } = useDraggableElement();

    const positionStyle = position.hasDragged ? { ...position } : defaultPositionStyle;

    return renderElement(
        <div
            ref={draggableRef}
            className='position-fixed rounded bg-primary padding-left-10 padding-right-20 padding-y-10 shadow-focused z-index-max'
            style={positionStyle}
        >
            <div className='display-flex align-items-center gap-10'>
                <span
                    role='button'
                    aria-label='Drag panel'
                    className={classNames(
                        'rioglyph rioglyph-drag-n-drop text-size-20',
                        isDragging ? 'cursor-grabbing' : 'cursor-grab'
                    )}
                    onPointerDown={onDragHandleDown}
                />
                <div className='display-flex flex-column line-height-14'>
                    <b>Draggable demo panel</b>
                    <span className='text-size-12'>Drag only with the icon on the left</span>
                </div>
            </div>
        </div>
    );
};

export default () => {
    const [show, setShow] = useState(false);
    const [instanceKey, setInstanceKey] = useState(0);

    return (
        <div>
            <p>
                Use the drag icon to move the fixed panel around the viewport. Viewport mode automatically renders
                through the shared portal root.
            </p>
            <ButtonToolbar>
                <Button onClick={() => setShow(!show)}>{show ? 'Hide' : 'Show'} draggable element</Button>
                <Button onClick={() => setInstanceKey(previous => previous + 1)}>Reset panel position</Button>
            </ButtonToolbar>
            {show && <DraggablePanel key={instanceKey} />}
        </div>
    );
};
```

#### HTML (html)

```html
<div>
    <p>Use the drag icon to move the fixed panel around the viewport. Viewport mode automatically renders through the shared portal root.</p>
    <div class="btn-toolbar">
        <button type="button" class="btn btn-default btn-component" tabindex="0">Show draggable element</button>
        <button type="button" class="btn btn-default btn-component" tabindex="0">Reset panel position</button>
    </div>
</div>
```

#### Props: Options (UseDraggableElementOptions)

### Options (UseDraggableElementOptions)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| positioning | 'viewport' \| 'container' | — | Defines whether drag coordinates are resolved against the viewport or a collision container. Viewport mode also enables portal rendering via renderElement. |
| initialPosition | 'center' \| Pick<DraggablePosition, 'top' \| 'left'> | — | Optional initial position for the draggable element. Use center to center the element inside the active positioning context on mount. |
| collisionContainerRef | RefObject<HTMLElement \| null> | — | Optional container whose visible client rect is used as the drag boundary in container mode. |

#### Props: Return value (UseDraggableResult)

### Return value (UseDraggableResult)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| draggableRef | MutableRefObject<T \| null> | — | Ref that must be attached to the draggable element container. |
| isDragging | boolean | — | Indicates whether a drag interaction is currently active. |
| position | DraggablePosition | — | Current draggable coordinates and drag-state metadata. |
| onDragHandleDown | (event: ReactPointerEvent<HTMLElement>) => void | — | Pointer down handler for the dedicated drag handle element. |
| renderElement | (element: ReactNode, target?: HTMLElement \| null) => ReactNode | — | Renders the provided element through the shared portal root in viewport mode. An optional custom target can be passed for specialized overlay hosts. In container mode, the element is returned unchanged and this helper is usually not needed. |

#### Props: Position object (DraggablePosition)

### Position object (DraggablePosition)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| top | number | — | Top offset in pixels relative to the active positioning context. |
| left | number | — | Left offset in pixels relative to the active positioning context. |
| hasDragged | boolean | — | Indicates whether the element has been moved by dragging at least once. |

## Draggable element inside container

### Example: Example 2

Use the drag icon to move the card inside the dashed boundary container.
Show draggable card Reset card position
Drag boundary container
Container mode keeps the draggable element inside this box and aligned correctly while the page scrolls.

#### React (tsx)

```tsx
import { useRef, useState, type RefObject } from 'react';

import Button from '@rio-cloud/rio-uikit/Button';
import ButtonToolbar from '@rio-cloud/rio-uikit/ButtonToolbar';
import Card from '@rio-cloud/rio-uikit/Card';
import useDraggableElement from '@rio-cloud/rio-uikit/hooks/useDraggableElement';

type DraggableCardProps = {
    onClose: () => void;
    collisionContainerRef: RefObject<HTMLDivElement | null>;
};

const DraggableCard = (props: DraggableCardProps) => {
    const { onClose, collisionContainerRef } = props;

    const { draggableRef, isDragging, position, onDragHandleDown } = useDraggableElement<HTMLDivElement>({
        positioning: 'container',
        initialPosition: 'center',
        collisionContainerRef,
    });

    return (
        <div ref={draggableRef} className='position-absolute z-index-1' style={{ ...position }}>
            <Card className='width-350 shadow-smooth' padding={20}>
                <div className='display-flex align-items-center justify-content-between gap-10'>
                    <div className='display-flex align-items-center gap-10'>
                        <div className='margin-left--5'>
                            <span
                                role='button'
                                aria-label='Drag card'
                                className='rioglyph rioglyph-drag-n-drop text-size-20 text-color-dark hover-text-color-darker'
                                style={{
                                    cursor: isDragging ? 'grabbing' : 'grab',
                                    touchAction: 'none',
                                }}
                                onPointerDown={onDragHandleDown}
                            />
                        </div>
                        <div className='text-size-16 text-medium text-color-darker'>Draggable card</div>
                    </div>
                    <Button
                        bsSize='xs'
                        bsStyle='muted'
                        onClick={onClose}
                        iconName='rioglyph-remove'
                        iconOnly
                        className='position-absolute top-15 right-15 hover-bg-none'
                    />
                </div>

                <div className='margin-top-15'>
                    This card is absolutely positioned inside the dashed container, so scrolling keeps it in sync with
                    its boundary.
                </div>
            </Card>
        </div>
    );
};

export default () => {
    const [show, setShow] = useState(false);
    const [instanceKey, setInstanceKey] = useState(0);

    const collisionContainerRef = useRef<HTMLDivElement>(null);

    const handleResetPosition = () => setInstanceKey(previous => previous + 1);

    return (
        <div>
            <p>Use the drag icon to move the card inside the dashed boundary container.</p>
            <ButtonToolbar>
                <Button onClick={() => setShow(!show)}>{show ? 'Hide' : 'Show'} draggable card</Button>
                <Button onClick={handleResetPosition}>Reset card position</Button>
            </ButtonToolbar>
            <div
                ref={collisionContainerRef}
                className='position-relative margin-top-20 min-height-300 rounded border border-style-dashed border-color-info bg-highlight-decent padding-20'
            >
                <div className='text-size-16 text-medium text-color-info'>Drag boundary container</div>
                <div className='margin-top-10 max-width-450 text-size-13 text-color-darker'>
                    Container mode keeps the draggable element inside this box and aligned correctly while the page
                    scrolls.
                </div>
                {show && (
                    <DraggableCard
                        key={instanceKey}
                        onClose={() => setShow(false)}
                        collisionContainerRef={collisionContainerRef}
                    />
                )}
            </div>
        </div>
    );
};
```

#### HTML (html)

```html
<div>
    <p>Use the drag icon to move the card inside the dashed boundary container.</p>
    <div class="btn-toolbar">
        <button type="button" class="btn btn-default btn-component" tabindex="0">Show draggable card</button>
        <button type="button" class="btn btn-default btn-component" tabindex="0">Reset card position</button>
    </div>
    <div class="position-relative margin-top-20 min-height-300 rounded border border-style-dashed border-color-info bg-highlight-decent padding-20">
        <div class="text-size-16 text-medium text-color-info">Drag boundary container</div>
        <div class="margin-top-10 max-width-450 text-size-13 text-color-darker">Container mode keeps the draggable element inside this box and aligned correctly while the page scrolls.</div>
    </div>
</div>
```

#### Props: Options (UseDraggableElementOptions)

### Options (UseDraggableElementOptions)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| positioning | 'viewport' \| 'container' | — | Defines whether drag coordinates are resolved against the viewport or a collision container. Viewport mode also enables portal rendering via renderElement. |
| initialPosition | 'center' \| Pick<DraggablePosition, 'top' \| 'left'> | — | Optional initial position for the draggable element. Use center to center the element inside the active positioning context on mount. |
| collisionContainerRef | RefObject<HTMLElement \| null> | — | Optional container whose visible client rect is used as the drag boundary in container mode. |

#### Props: Return value (UseDraggableResult)

### Return value (UseDraggableResult)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| draggableRef | MutableRefObject<T \| null> | — | Ref that must be attached to the draggable element container. |
| isDragging | boolean | — | Indicates whether a drag interaction is currently active. |
| position | DraggablePosition | — | Current draggable coordinates and drag-state metadata. |
| onDragHandleDown | (event: ReactPointerEvent<HTMLElement>) => void | — | Pointer down handler for the dedicated drag handle element. |
| renderElement | (element: ReactNode, target?: HTMLElement \| null) => ReactNode | — | Renders the provided element through the shared portal root in viewport mode. An optional custom target can be passed for specialized overlay hosts. In container mode, the element is returned unchanged and this helper is usually not needed. |

#### Props: Position object (DraggablePosition)

### Position object (DraggablePosition)

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| top | number | — | Top offset in pixels relative to the active positioning context. |
| left | number | — | Left offset in pixels relative to the active positioning context. |
| hasDragged | boolean | — | Indicates whether the element has been moved by dragging at least once. |