Floating Panel
Used to display content in a non-modal floating window.
Anatomy
To set up the editable correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Examples
Learn how to use the FloatingPanel
component in your project. Let's take a look at the most basic example:
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
export const Basic = () => (
<FloatingPanel.Root>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
export const Basic = () => (
<FloatingPanel.Root>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
</script>
<template>
<FloatingPanel.Root>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Controlling the size
To control the size of the floating panel programmatically, you can pass the size
onResize
prop to the machine.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
import { useState } from 'react'
export const ControlledSize = () => {
const [size, setSize] = useState({ width: 400, height: 300 })
return (
<FloatingPanel.Root size={size} onSizeChange={(e) => setSize(e.size)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
export const ControlledSize = () => {
const [size, setSize] = createSignal({ width: 400, height: 300 })
return (
<FloatingPanel.Root size={size()} onSizeChange={(e) => setSize(e.size)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const size = ref({ width: 400, height: 300 })
</script>
<template>
<FloatingPanel.Root v-model:size="size">
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Controlling the position
To control the position of the floating panel programmatically, you can pass the position
and onPositionChange
prop
to the machine.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
import { useState } from 'react'
export const ControlledPosition = () => {
const [position, setPosition] = useState({ x: 200, y: 200 })
return (
<FloatingPanel.Root position={position} onPositionChange={(e) => setPosition(e.position)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
export const ControlledPosition = () => {
const [position, setPosition] = createSignal({ x: 200, y: 200 })
return (
<FloatingPanel.Root position={position()} onPositionChange={(e) => setPosition(e.position)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const position = ref({ x: 200, y: 200 })
</script>
<template>
<FloatingPanel.Root v-model:position="position">
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Anchor position
Use the getAnchorPosition
function to compute the initial position of the floating panel. This function is called when
the panel is opened and receives the triggerRect
and boundaryRect
.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
export const AnchorPosition = () => (
<FloatingPanel.Root
getAnchorPosition={({ triggerRect }) => {
if (!triggerRect) return { x: 0, y: 0 }
return {
x: triggerRect.x + triggerRect.width / 2,
y: triggerRect.y + triggerRect.height / 2,
}
}}
>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
export const AnchorPosition = () => (
<FloatingPanel.Root
getAnchorPosition={({ triggerRect }) => {
if (!triggerRect) return { x: 0, y: 0 }
return {
x: triggerRect.x + triggerRect.width / 2,
y: triggerRect.y + triggerRect.height / 2,
}
}}
>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
interface Rect {
x: number
y: number
width: number
height: number
}
interface AnchorPositionDetails {
triggerRect: Rect | null
boundaryRect: Rect | null
}
const getAnchorPosition = ({ triggerRect }: AnchorPositionDetails) => {
if (!triggerRect) return { x: 0, y: 0 }
return {
x: triggerRect.x + triggerRect.width / 2,
y: triggerRect.y + triggerRect.height / 2,
}
}
</script>
<template>
<FloatingPanel.Root :get-anchor-position="getAnchorPosition">
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Controlling the open state
To control the open state of the floating panel programmatically, you can pass the open
and onOpenChange
prop to the
machine.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
import { useState } from 'react'
export const ControlledOpen = () => {
const [open, setOpen] = useState(false)
return (
<FloatingPanel.Root open={open} onOpenChange={(e) => setOpen(e.open)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
export const ControlledOpen = () => {
const [open, setOpen] = createSignal(false)
return (
<FloatingPanel.Root open={open()} onOpenChange={(e) => setOpen(e.open)}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
}
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<FloatingPanel.Root v-model:open="open">
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Lazy mounting
To lazy mount the floating panel, you can pass the lazyMount
prop to the machine.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
export const LazyMount = () => (
<FloatingPanel.Root lazyMount onExitComplete={() => console.log('onExitComplete invoked')}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
export const LazyMount = () => (
<FloatingPanel.Root lazyMount onExitComplete={() => console.log('onExitComplete invoked')}>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
</script>
<template>
<FloatingPanel.Root lazy-mount @exit-complete="() => console.log('onExitComplete invoked')">
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
Context
To access the context of the floating panel, you can use the useFloatingPanelContext
hook or the
FloatingPanel.Context
component.
import { FloatingPanel } from '@ark-ui/react/floating-panel'
import { Portal } from '@ark-ui/react/portal'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-react'
export const RenderFn = () => (
<FloatingPanel.Root>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<FloatingPanel.Context>
{(floatingPanel) => <p>floatingPanel. is {floatingPanel.open ? 'open' : 'closed'}</p>}
</FloatingPanel.Context>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
export const RenderFn = () => (
<FloatingPanel.Root>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<FloatingPanel.Context>
{(floatingPanel) => <p>floatingPanel is {floatingPanel().open ? 'open' : 'closed'}</p>}
</FloatingPanel.Context>
<Portal>
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
<script setup lang="ts">
import { FloatingPanel } from '@ark-ui/vue/floating-panel'
import { ArrowDownLeft, Maximize2, Minus, XIcon } from 'lucide-vue-next'
</script>
<template>
<FloatingPanel.Root>
<FloatingPanel.Context v-slot="floatingPanel">
<p>floatingPanel is {{ floatingPanel.open ? 'open' : 'closed' }}</p>
</FloatingPanel.Context>
<FloatingPanel.Trigger>Toggle Panel</FloatingPanel.Trigger>
<Teleport to="body">
<FloatingPanel.Positioner>
<FloatingPanel.Content>
<FloatingPanel.DragTrigger>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.Control>
<FloatingPanel.StageTrigger stage="minimized">
<Minus />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="maximized">
<Maximize2 />
</FloatingPanel.StageTrigger>
<FloatingPanel.StageTrigger stage="default">
<ArrowDownLeft />
</FloatingPanel.StageTrigger>
<FloatingPanel.CloseTrigger>
<XIcon />
</FloatingPanel.CloseTrigger>
</FloatingPanel.Control>
</FloatingPanel.Header>
</FloatingPanel.DragTrigger>
<FloatingPanel.Body>
<p>Some content</p>
</FloatingPanel.Body>
<FloatingPanel.ResizeTrigger axis="n" />
<FloatingPanel.ResizeTrigger axis="e" />
<FloatingPanel.ResizeTrigger axis="w" />
<FloatingPanel.ResizeTrigger axis="s" />
<FloatingPanel.ResizeTrigger axis="ne" />
<FloatingPanel.ResizeTrigger axis="se" />
<FloatingPanel.ResizeTrigger axis="sw" />
<FloatingPanel.ResizeTrigger axis="nw" />
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Teleport>
</FloatingPanel.Root>
</template>
API Reference
Root
Prop | Default | Type |
---|---|---|
allowOverflow | true | boolean Whether the panel should be strictly contained within the boundary when dragging |
closeOnEscape | boolean Whether the panel should close when the escape key is pressed | |
defaultOpen | false | boolean The initial open state of the panel when rendered. Use when you don't need to control the open state of the panel. |
defaultPosition | Point The initial position of the panel when rendered. Use when you don't need to control the position of the panel. | |
defaultSize | Size The default size of the panel | |
dir | 'ltr' | 'ltr' | 'rtl' The document's text/writing direction. |
disabled | boolean Whether the panel is disabled | |
draggable | true | boolean Whether the panel is draggable |
getAnchorPosition | (details: AnchorPositionDetails) => Point Function that returns the initial position of the panel when it is opened. If provided, will be used instead of the default position. | |
getBoundaryEl | () => HTMLElement | null The boundary of the panel. Useful for recalculating the boundary rect when the it is resized. | |
gridSize | 1 | number The snap grid for the panel |
id | string The unique identifier of the machine. | |
ids | Partial<{ trigger: string; positioner: string; content: string; title: string; header: string }> The ids of the elements in the floating panel. Useful for composition. | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | boolean Whether to enable lazy mounting |
lockAspectRatio | boolean Whether the panel is locked to its aspect ratio | |
maxSize | Size The maximum size of the panel | |
minSize | Size The minimum size of the panel | |
onExitComplete | VoidFunction Function called when the animation ends in the closed state | |
onOpenChange | (details: OpenChangeDetails) => void Function called when the panel is opened or closed | |
onPositionChange | (details: PositionChangeDetails) => void Function called when the position of the panel changes via dragging | |
onPositionChangeEnd | (details: PositionChangeDetails) => void Function called when the position of the panel changes via dragging ends | |
onSizeChange | (details: SizeChangeDetails) => void Function called when the size of the panel changes via resizing | |
onSizeChangeEnd | (details: SizeChangeDetails) => void Function called when the size of the panel changes via resizing ends | |
onStageChange | (details: StageChangeDetails) => void Function called when the stage of the panel changes | |
open | boolean The controlled open state of the panel | |
persistRect | boolean Whether the panel size and position should be preserved when it is closed | |
position | Point The controlled position of the panel | |
present | boolean Whether the node is present (controlled by the user) | |
resizable | true | boolean Whether the panel is resizable |
size | Size The size of the panel | |
skipAnimationOnMount | false | boolean Whether to allow the initial presence animation. |
strategy | 'fixed' | 'absolute' | 'fixed' The strategy to use for positioning |
translations | IntlTranslations The translations for the floating panel. | |
unmountOnExit | false | boolean Whether to unmount on exit. |
Body
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | body |
[data-dragging] | Present when in the dragging state |
CloseTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Content
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | content |
[data-state] | "open" | "closed" |
[data-dragging] | Present when in the dragging state |
[data-topmost] | |
[data-behind] |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
DragTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | drag-trigger |
[data-disabled] | Present when disabled |
Header
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | header |
[data-dragging] | Present when in the dragging state |
[data-topmost] | |
[data-behind] |
Positioner
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ResizeTrigger
Prop | Default | Type |
---|---|---|
axis | ResizeTriggerAxis The axis of the resize handle | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | resize-trigger |
[data-disabled] | Present when disabled |
[data-axis] | The axis to resize |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseFloatingPanelReturn | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | boolean Whether to enable lazy mounting |
onExitComplete | VoidFunction Function called when the animation ends in the closed state | |
skipAnimationOnMount | false | boolean Whether to allow the initial presence animation. |
unmountOnExit | false | boolean Whether to unmount on exit. |
StageTrigger
Prop | Default | Type |
---|---|---|
stage | Stage The stage of the panel | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Title
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Trigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | floating-panel |
[data-part] | trigger |
[data-state] | "open" | "closed" |
[data-dragging] | Present when in the dragging state |