Uses Svelte 5 runes to wrap floating-ui with additional features like tethering and conditional triggers
npm install floating-runesSvelte 5 Runes powered wrapper for @floating-ui. An alternative approach to svelte-floating-ui which approx. does the same thing.
floating-runes will also:
- Position arrow/floater automatically (unless autoPosition: false is provided)
- Access the elements via $state for .referenced, .tethered, .attached (tethered ?? referenced)
- Tethering (temporary element reference)
- Virtual positioning for cursor/selection-based UIs
- Conditional reference/tethering
- A portal action
- An overlay action with optional scroll locking
- A createSingleton factory for singleton UI patterns
Other than that, just use it as you would use @floating-ui🎉
Happy coding!🦒
Usage
1. Simple example
1. Tethering
1. Virtual positioning
1. Advanced use
1. Portal action
1. Overlay action
1. Singleton pattern
Options and properties
1. floatingUI
1. use:float
1. float.ref and float.tether
1. float.unref and float.untether
1. float.virtual and float.unvirtual
1. float.placement
1. Additional exports
1. createSingleton
bun add floating-runes
- use:float - Designating the floating elements
- use:float.arrow - Designated arrow element; must be a direct child element of use:float
- use:float.ref - The thing the floated element(s) is referencing
Svelte Playground - Usage example
``html
> [!TIP]
> P.S. you can use multiple
use:float from the same declaration.
#### Tethering
You can use
float.tether(element) to float to another element than the float.ref. Then use float.untether() and it returns to float.ref.`html
{#snippet href(ref, text)}
class:active={ref === url}
use:float.ref={() => ref === url}
use:float.tether={'pointerenter'}
href={ref}
>
{text}
{/snippet}
{#if float.tethered}
{/if}
{@render href('/a', 'Hover me')}
{@render href('/b', 'I want attention')}
{@render href('/c', 'Guys... what about meeEeEe')}
{@render href('/d', 'Ignore my brother')}
`
#### Virtual positioning
Position relative to a virtual point (mouse cursor, selection range, etc.).
float.virtual(...) supports:
- VirtualElement: float.virtual({ getBoundingClientRect: () => ... })
- Reactive getter: float.virtual(() => ({ x, y })) (updates when dependencies change)
- Action + event: use:float.virtual={'pointermove'} (tracks the pointer)float.unvirtual(...) clears the virtual reference and can also be event-driven.`html
use:float.virtual={'pointermove'}
use:float.unvirtual={'pointerleave'}
>
Hover me
{#if float.referenced}
For context menus, you can also set a custom virtual element based on the event:
`html
{
float.virtual({
getBoundingClientRect: () => ({
width: 0,
height: 0,
x: e.clientX,
y: e.clientY,
top: e.clientY,
left: e.clientX,
right: e.clientX,
bottom: e.clientY
})
})
}}>
Right click me
`
#### Advanced
As per the documentation of @floating-ui,
you can access the .then(...) which works in the same way as their documentation.
So you can go wild🦒
`html
`
#### Bonus
As a bonus, you can use
portal to move an element to another (such as the body).When the component is destroyed, the element that was portalled, will naturally, also get destroyed.
`html
I'm in the body😏
I'm in another element
`
#### Overlay action
Create a full-screen backdrop and optionally lock body scroll (default:
true).`html
`Disable scroll locking:
`html
`
$3
#### floatingUI
FloatingRuneOptions extends ComputePositionConfig
| Property | Type | Description |
| --- | --- | --- |
| middleware? | Middleware[] | Array of middleware objects to modify the positioning or provide data for rendering |
| platform? | Platform | Custom or extended platform object |
| placement? |
\| 'top'
\| 'top-start'
\| 'top-end'
\| 'right'
\| 'right-start'
\| 'right-end'
\| 'bottom'
\| 'bottom-start'
\| 'bottom-end'
\| 'left'
\| 'left-start'
\| 'left-end' | Where to place the floating element relative to its reference element
Default: 'bottom' |
| strategy? | 'absolute' \| 'fixed' | The type of CSS position property to use
Default: absolute |
| autoUpdate? | AutoUpdateOptions | Whether or not to auto-update the floating element's position |
| autoPosition? | boolean | Whether or not to auto-position the floating element and the arrow, and auto-assign the position: to the strategy (absolute/fixed)
Default: true |> [!NOTE]
> The
arrow middleware does not take an element property. Instead apply the Svelte action use:float.arrow#####
.then(...)Read more about
const float = floatingUI(...).then((data: ComputePositionReturn) => void)
#####
float.referenced, float.tethered and float.attachedThe element that has been referenced to, or tethered to. Attached will return
tethered ?? referenced
#### float
use:floatThis Svelte action creates a floater, that floats relative to the reference- and tethered element.
use:float={FloatOptions}| Property | Type | Description |
| --- | --- | --- |
|
tether | boolean | Whether-to-tether.
Default: true |
| untether | boolean | If false it will stick to the last tethered target,
instead of going back to the reference.
Default: true |
####
float.arrowuse:float.arrowThis Svelte action creates reference to the element that serves as the arrow to a
use:float element. Must be a direct child.`html
...
...
`> [!TIP]
> Remember to include the
arrow middleware,
> and put it after other middlewares if needed.The arrow element also receives CSS custom properties that reflect the computed placement:
-
--float-side: top | right | bottom | left
- --float-rotation: 0deg | 90deg | 180deg | 270deg
- --float-placement: full placement string (e.g. top-start)
####
float.ref and float.tetheruse:float.ref and use:float.tetherThese Svelte actions sets the reference point for the
use:float element.Additionally, they accept a trigger parameter: A conditional callback (
() => boolean) or an event (keyof WindowEventMap).Ex.
use:float.ref={() => url === href}
or
use:float.tether={'pointerenter'}
####
float.unref and float.untetherfloat.unref removes the current reference.float.untether removes the tethering, so that the floating element will return to the reference (unless untether: false is provided).Both can be used directly either via
float.unref() / float.untether()Or like
float.ref and float.tether have a condition to trigger;Ex.
use:float.untether={() => condition}
or
use:float.unref={'pointerleave'}
####
float.virtual and float.unvirtualSet or clear a virtual reference.
VirtualElement is re-exported from @floating-ui/dom.`html
Follow the cursor
``html
{ x = e.clientX; y = e.clientY }}>
Track
{#if float.referenced}
Cursor: {x}, {y}
{/if}
`
####
float.placementReactive getter for the computed placement (updates on flip/shift).
`html
Tooltip
Placement: {float.placement}
`
#### Additional exports
-
detectOverflow (re-export from @floating-ui/dom)
- VirtualElement type (re-export from @floating-ui/dom)
####
createSingletonCreate a callable singleton action that can be exported from
{#if tooltip.visible && tooltip.content !== undefined}
Use it anywhere by importing the module export:
`svelte
`#### Programmatic control
`ts
tooltip.show('Hello', buttonEl)
tooltip.show('At cursor', { x: event.clientX, y: event.clientY })
tooltip.hide()
`For context menus, combine singleton usage with virtual positioning.
#### Singleton options
| Option | Type | Description |
| --- | --- | --- |
|
middleware? | Middleware[] | Floating UI middleware array
Default: [offset(8), flip(), shift(), arrow()] |
| showDelay? | number | Delay before showing (ms)
Default: 0 |
| hideDelay? | number | Delay before hiding (ms)
Default: 0 |
| showOn? | TriggerEvent \| TriggerEvent[] | Events that show the singleton
Default: ['pointerenter', 'focus'] |
| hideOn? | TriggerEvent \| TriggerEvent[] | Events that hide the singleton
Default: ['pointerleave', 'blur'] |#### Singleton API
| Property | Type | Description |
| --- | --- | --- |
|
visible | boolean | Whether the floating element is currently visible |
| content | string \| Snippet | Current content to display |
| anchor | HTMLElement | Current anchor element (DOM triggers only) |
| placement | Placement | Computed placement after positioning |
| float | Action | Action to apply to the floating element |
| arrow | Action | Action to apply to the arrow element |
| show(content, anchor?) | function | Programmatically show, optionally at an element or { x, y } |
| hide() | function` | Programmatically hide |