Primitive API / Page 04·04
FlexiGrab
A grab handle for a widget. Use it when only part of a widget should start a drag, leaving the rest free for buttons, links, and text selection.
FlexiGrab (component)
| Name | Description |
|---|---|
children Optional Snippet<[{ widget: FlexiWidgetController }]> | The content of the handle. Receives the surrounding widget's controller. |
class Optional ClassValue | Classes applied to the rendered button. |
FlexiGrab renders a button inside a FlexiWidget. Once a widget contains at least one FlexiGrab, only its grab handles start a drag; pointer events elsewhere on the widget behave normally. The button is disabled while the widget’s draggability is not 'full'.
Svelte Grab handle
Only the handle drags me
Select my text freely
<script lang="ts">
import { FlexiBoard, FlexiTarget, FlexiWidget, FlexiGrab } from '@flexiboards/svelte';
import GripVertical from 'lucide-svelte/icons/grip-vertical';
</script>
<FlexiBoard class="w-72 rounded-xl border p-6 lg:w-96">
<FlexiTarget
class="gap-3"
config={{ layout: { type: 'flow', flowAxis: 'row', placementStrategy: 'append' } }}
>
{#each ['Only the handle drags me', 'Select my text freely'] as label}
<FlexiWidget
class={(widget) => [
'bg-muted flex items-center gap-3 rounded-lg px-3 py-2',
widget.isShadow && 'opacity-50'
]}
>
<FlexiGrab class="hover:bg-background rounded p-1">
<GripVertical class="size-4" />
<span class="sr-only">Move widget</span>
</FlexiGrab>
<span>{label}</span>
</FlexiWidget>
{/each}
</FlexiTarget>
</FlexiBoard><script lang="ts">
import { FlexiBoard, FlexiTarget, FlexiWidget, FlexiGrab } from '@flexiboards/svelte';
import GripVertical from 'lucide-svelte/icons/grip-vertical';
</script>
<FlexiBoard class="w-72 rounded-xl border p-6 lg:w-96">
<FlexiTarget
class="gap-3"
config={{ layout: { type: 'flow', flowAxis: 'row', placementStrategy: 'append' } }}
>
{#each ['Only the handle drags me', 'Select my text freely'] as label}
<FlexiWidget
class={(widget) => [
'bg-muted flex items-center gap-3 rounded-lg px-3 py-2',
widget.isShadow && 'opacity-50'
]}
>
<FlexiGrab class="hover:bg-background rounded p-1">
<GripVertical class="size-4" />
<span class="sr-only">Move widget</span>
</FlexiGrab>
<span>{label}</span>
</FlexiWidget>
{/each}
</FlexiTarget>
</FlexiBoard>
FlexiGrab has no controller of its own. Inside its content you receive the surrounding widget’s controller, so the handle can reflect the widget’s state:
<FlexiGrab>
{#snippet children({ widget })}
<GripVertical class={widget.isGrabbed ? 'text-primary' : undefined} />
{/snippet}
</FlexiGrab> Accessibility
- The handle is a native
button, so it is focusable with Tab and disabled when the widget cannot be grabbed. - Give it a text label. An icon-only handle should contain a visually hidden
span(for example, Tailwind’ssr-only) reading “Move widget”. - Once a widget has a grab handle, the widget itself is no longer focusable, and keyboard grabbing moves to the handle: Enter grabs, the arrow keys move, Enter drops, Escape cancels. See Accessibility for the full keyboard model.
Gotchas
- A
FlexiGrabmust be rendered inside aFlexiWidget. Outside one, it throws. - The handle sets
touch-action: noneon itself so touch drags start immediately. Keep it small, or scrolling on touch devices becomes hard when a finger lands on it.