npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-floating-attach

v1.0.7

Published

Svelte 5 attachment-based wrapper for @floating-ui/dom

Readme

version downloads

Svelte attachment for floating-ui. Position floating elements like tooltips, popovers, and dropdowns with automatic reactivity - most modern floating-ui wrapper for svelte.

Installation and requirement

npm install svelte-floating-attach @floating-ui/dom

@floating-ui/dom is a peer dependency, not bundled into this package. You need to install it alongside to prevent version conflicts.

  • Svelte >=5.29.0 (attachment support)
  • @floating-ui/dom >=1.6.0

Why this library?

This library is heavily inspired by svelte-floating-ui. If you're on ≤ Svelte 5.29 you should use that library instead.

svelte-floating-attach requires ≥ Svelte 5.29. It uses attachments ({@attach}) API. Attachments work better than actions.

What's different from svelte-floating-ui?

| | svelte-floating-attach | svelte-floating-ui | | --------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Reactivity | Automatic — attachment re-runs when any $state in its arguments changes | Manual (if using runes) — requires $effect + update() to sync prop changes | | Arrow/caret positioning | Automatic — left/top styles applied to the arrow element after each computation | Manual — you read middlewareData.arrow in onComputed and apply styles yourself | | @floating-ui/dom | Peer dependency — installed separately, no duplicates | Direct dependency — bundled in, may duplicate if you also depend on it directly | | Svelte stores | None — plain closures | Uses writable stores for arrow refs and virtual elements | | Bundle footprint | ~5 KB compiled (re-exports from your existing @floating-ui/dom) | Bundles its own copy of @floating-ui/dom into the package |

Automatic reactivity

Actions (use:) don't re-run when their arguments change. With svelte-floating-ui, you need a manual $effect to push updated options:

<script>
  import { createFloatingActions } from 'svelte-floating-ui'
  import { flip, offset, shift } from 'svelte-floating-ui/dom'

  let { placement = $bindable('bottom') } = $props()

  const [floatingRef, floatingContent, updatePosition] = createFloatingActions({
    placement,
    middleware: [offset(8), shift(), flip()],
  })

  // Required: manually sync reactive props to the action
  $effect(() => {
    updatePosition({ placement })
  })
</script>

<button use:floatingRef>Trigger</button>
<div use:floatingContent>Content</div>

Attachments run in the template's reactive tracking context — when placement changes, the attachment tears down and re-runs with the new value. No $effect, no update():

<script>
  import { createFloating, flip, offset, shift } from 'svelte-floating-attach'

  let { placement = $bindable('bottom') } = $props()

  const { ref, content } = createFloating()
</script>

<button {@attach ref}>Trigger</button>
<div {@attach content({
  placement,
  middleware: [offset(8), shift(), flip()],
})}>
  Content
</div>

Automatic arrow/caret positioning

In svelte-floating-ui, you create a writable store for the arrow element and manually apply its computed position inside onComputed:

<script>
  import { createFloatingActions, arrow } from 'svelte-floating-ui'
  import { createArrowRef } from 'svelte-floating-ui'

  const arrowRef = createArrowRef()

  const [floatingRef, floatingContent] = createFloatingActions({
    middleware: [arrow({ element: arrowRef })],
    onComputed({ placement, middlewareData }) {
      // You must manually read and apply arrow styles
      const { x, y } = middlewareData.arrow
      const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[
        placement.split('-')[0]
      ]
      Object.assign($arrowRef.style, {
        left: x != null ? `${x}px` : '',
        top: y != null ? `${y}px` : '',
        [staticSide]: '-4px',
      })
    },
  })
</script>

<div use:floatingContent>
  Content
  <div bind:this={$arrowRef} class="arrow"></div>
</div>

In svelte-floating-attach, this is built in. The library automatically applies left/top from middlewareData.arrow to the arrow element after every computation, and resets stale right/bottom when placement changes:

<script>
  import { createFloating, offset, flip, shift } from 'svelte-floating-attach'

  const { ref, content, arrow, arrowMiddleware } = createFloating()
</script>

<div {@attach content({
  placement: 'top',
  middleware: [offset(8), flip(), shift(), arrowMiddleware({ padding: 4 })],
})}>
  Content
  <div {@attach arrow} class="arrow"></div>
</div>

Usage

Basic Popover

<script>
  import { createFloating, offset, flip, shift } from 'svelte-floating-attach'

  let show = $state(false)
  const { ref, content } = createFloating()
</script>

<button {@attach ref} onclick={() => show = !show}>
  Toggle
</button>

{#if show}
  <div {@attach content({
    placement: 'bottom',
    middleware: [offset(8), flip(), shift()],
  })}>
    Popover content
  </div>
{/if}

Reactive Placement (Component Props)

A complete example showing how placement reacts to prop changes and how onComputed keeps the actual placement in sync when Floating UI flips it due to lack of space.

<!-- Popover.svelte -->
<script lang="ts">
  import type { Snippet } from 'svelte'
  import type { Placement } from 'svelte-floating-attach'
  import { createFloating, offset, flip, shift, hide } from 'svelte-floating-attach'

  interface Props {
    /** Preferred placement — may be overridden by flip() */
    placement?: Placement
    show?: boolean
    children?: Snippet
    content?: Snippet
  }

  let {
    placement = $bindable('bottom'),
    show = $bindable(false),
    children,
    content: contentSnippet,
  }: Props = $props()

  const { ref, content: floatingContent } = createFloating()
</script>

<button {@attach ref} onclick={() => show = !show}>
  {@render children?.()}
</button>

{#if show}
  <div {@attach floatingContent({
    placement,
    middleware: [offset(8), shift(), flip(), hide()],
    onComputed: (data) => (placement = data.placement),
  })}>
    {@render contentSnippet?.()}
  </div>
{/if}

When the consumer passes a different placement prop, the attachment re-runs automatically — no $effect needed. The onComputed callback writes back the actual placement so the consumer always knows where the popover ended up (e.g., 'top' instead of 'bottom' after a flip).

<!-- Consumer.svelte -->
<script>
  import Popover from './Popover.svelte'

  let placement = $state('bottom')
</script>

<select bind:value={placement}>
  <option value="top">Top</option>
  <option value="bottom">Bottom</option>
  <option value="left">Left</option>
  <option value="right">Right</option>
</select>

<Popover bind:placement>
  {#snippet content()}
    Placed at: {placement}
  {/snippet}
  Click me
</Popover>

Tooltip with Arrow

The library automatically applies left/top from middlewareData.arrow to the arrow element, positioning it along the edge to stay centered on the reference. It also resets stale right/bottom values when placement changes.

<script>
  import { createFloating, offset, flip, shift } from 'svelte-floating-attach'

  let show = $state(false)
  const { ref, content, arrow, arrowMiddleware } = createFloating()
</script>

<button
  {@attach ref}
  onmouseenter={() => show = true}
  onmouseleave={() => show = false}
>
  Hover me
</button>

{#if show}
  <div {@attach content({
    placement: 'top',
    middleware: [offset(8), flip(), shift(), arrowMiddleware({ padding: 4 })],
  })}>
    Tooltip text
    <div {@attach arrow} class="arrow"></div>
  </div>
{/if}

Pushing the arrow onto the edge

The library positions the arrow along the correct edge (centering it on the reference) but does not push it onto the edge itself — that depends on your arrow's size and visual design. Use onComputed to offset the arrow so it pokes out of the floating element:

<script>
  import { createFloating, offset, flip, shift } from 'svelte-floating-attach'

  let show = $state(false)
  let arrowEl = $state()
  const { ref, content, arrow, arrowMiddleware } = createFloating()

  function onComputed(data) {
    if (!arrowEl) return

    // The side of the floating element that faces the reference:
    //   placement "top"    → arrow sits on "bottom" edge
    //   placement "bottom" → arrow sits on "top" edge
    //   placement "left"   → arrow sits on "right" edge
    //   placement "right"  → arrow sits on "left" edge
    const side = data.placement.split('-')[0]
    const oppositeSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[side]

    // Nudge the arrow outward so it straddles the edge (half in, half out)
    arrowEl.style[oppositeSide] = `${-arrowEl.offsetWidth / 2}px`
  }
</script>

<button
  {@attach ref}
  onmouseenter={() => show = true}
  onmouseleave={() => show = false}
>
  Hover me
</button>

{#if show}
  <div {@attach content({
    placement: 'top',
    middleware: [offset(8), flip(), shift(), arrowMiddleware({ padding: 4 })],
    onComputed,
  })}>
    Tooltip text
    <div bind:this={arrowEl} {@attach arrow} class="arrow"></div>
  </div>
{/if}

<style>
  .arrow {
    position: absolute;
    width: 10px;
    height: 10px;
    background: inherit;
    transform: rotate(45deg);
  }
</style>

Without this offset the arrow stays fully inside the floating element. The offset value controls how much it pokes out — use -offsetWidth / 2 to center it on the edge, or any other value that fits your design.

Virtual Element

<script>
  import { createFloating, createVirtualElement, offset } from 'svelte-floating-attach'

  const virtual = createVirtualElement({
    getBoundingClientRect: { x: 0, y: 0, top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0 }
  })

  const { setVirtualReference, content } = createFloating()
  setVirtualReference(virtual)

  let show = $state(false)
</script>

<div
  onmouseenter={() => show = true}
  onmouseleave={() => show = false}
  onmousemove={(e) => {
    virtual.update({
      getBoundingClientRect: {
        x: e.clientX, y: e.clientY,
        top: e.clientY, left: e.clientX,
        bottom: e.clientY, right: e.clientX,
        width: 0, height: 0,
      }
    })
  }}
>
  Hover area
</div>

{#if show}
  <div {@attach content({ strategy: 'fixed', placement: 'right-start', middleware: [offset(16)] })}>
    Following cursor
  </div>
{/if}

API

createFloating()

Creates a floating instance. Returns:

| Property | Type | Description | | --------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | ref | Attachment | Attach to the reference/trigger element | | content | (options?) => Attachment | Returns an attachment for the floating element | | arrow | Attachment | Attach to the arrow/caret element. left/top styles are applied automatically from middlewareData.arrow after each position computation. | | arrowMiddleware | (options?) => Middleware | Creates arrow middleware using the captured arrow element. Use in the middleware array passed to content(). | | setVirtualReference | (el: VirtualElement) => void | Set a virtual element as the reference |

FloatingContentOptions

Options passed to content(). See Floating UI docs for details on placement, strategy, and middleware.

| Option | Type | Default | Description | | ------------ | --------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------- | | placement | Placement | 'bottom' | Where to place the floating element | | strategy | Strategy | 'absolute' | CSS positioning strategy | | middleware | Middleware[] | undefined | Floating UI middleware array | | autoUpdate | boolean \| AutoUpdateOptions | true | Auto-update on scroll/resize | | onComputed | (data: ComputePositionReturn) => void | undefined | Callback after position computation |

createVirtualElement(config)

Creates a mutable virtual element for non-DOM references (e.g., cursor position). Call .update(config) to change the position.

Re-exports

All middleware and types from @floating-ui/dom are re-exported for convenience, so you only need one import source.

License

MIT