Floating Panel
A shadcn-style floating panel component built with Ark UI primitives.
Floating panel
Drag the header, resize from edges, or use the window controls.
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import type { FloatingPanelProps } from "@/components/ui/floating-panel";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelDemoLayout = ({
title,
children,
...root
}: FloatingPanelProps & { title: string; children: React.ReactNode }) => (
<FloatingPanel defaultSize={{ width: 360, height: 220 }} {...root}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
{title}
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>{children}</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
const FloatingPanelBasicDemo = () => (
<FloatingPanelDemoLayout title="Floating panel">
<p>Drag the header, resize from edges, or use the window controls.</p>
</FloatingPanelDemoLayout>
);
export default FloatingPanelBasicDemo;
Installation
npx shadcn@latest add @ark-cn/floating-panelInstall the dependency required by this primitive:
npm install @ark-ui/reactCopy the component source into your app:
"use client";
import {
FloatingPanel as FloatingPanelPrimitive,
useFloatingPanelContext,
} from "@ark-ui/react/floating-panel";
import { Portal } from "@ark-ui/react/portal";
import { cn } from "@/lib/utils";
export type FloatingPanelProps = FloatingPanelPrimitive.RootProps;
const RESIZE_AXES = ["n", "e", "w", "s", "ne", "se", "sw", "nw"] as const;
export const FloatingPanelResizeEdges = ({
className,
}: {
className?: string;
}) => (
<>
{RESIZE_AXES.map((axis) => (
<FloatingPanelResizeTrigger
axis={axis}
className={className}
key={axis}
/>
))}
</>
);
export const FloatingPanel = (props: FloatingPanelProps) => (
<FloatingPanelPrimitive.Root data-slot="floating-panel" {...props} />
);
export type FloatingPanelPopupProps = FloatingPanelPrimitive.ContentProps & {
disablePortal?: boolean;
positionerClassName?: string;
};
export const FloatingPanelPopup = ({
className,
disablePortal,
positionerClassName,
children,
...contentProps
}: FloatingPanelPopupProps) => {
const inner = (
<FloatingPanelPrimitive.Positioner
className={cn(!disablePortal && "z-50", positionerClassName)}
data-slot="floating-panel-positioner"
>
<FloatingPanelPrimitive.Content
className={cn(
"relative flex w-full min-w-48 flex-col overflow-hidden rounded-xl border border-border/80 bg-popover text-popover-foreground shadow-md outline-none ring-1 ring-border/20 data-behind:opacity-40 data-maximized:rounded-none data-topmost:z-999999",
className,
)}
data-slot="floating-panel-content"
{...contentProps}
>
{children}
<FloatingPanelResizeEdges />
</FloatingPanelPrimitive.Content>
</FloatingPanelPrimitive.Positioner>
);
return disablePortal ? inner : <Portal>{inner}</Portal>;
};
export const FloatingPanelTrigger = ({
className,
...props
}: FloatingPanelPrimitive.TriggerProps) => (
<FloatingPanelPrimitive.Trigger
className={cn(
"inline-flex h-9 cursor-pointer items-center justify-center rounded-lg border border-input bg-background px-3 font-medium text-foreground text-sm shadow-xs/5 outline-none transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:h-8",
className,
)}
data-slot="floating-panel-trigger"
{...props}
/>
);
export const FloatingPanelDragTrigger = ({
className,
...props
}: FloatingPanelPrimitive.DragTriggerProps) => (
<FloatingPanelPrimitive.DragTrigger
className={cn("flex min-h-0 min-w-0 shrink-0 flex-col", className)}
data-slot="floating-panel-drag-trigger"
{...props}
/>
);
export const FloatingPanelHeader = ({
className,
...props
}: FloatingPanelPrimitive.HeaderProps) => (
<FloatingPanelPrimitive.Header
className={cn(
"flex shrink-0 items-center gap-2 border-border border-b bg-muted/30 px-3 py-2.5 data-dragging:cursor-grabbing data-dragging:select-none sm:gap-3",
className,
)}
data-slot="floating-panel-header"
{...props}
/>
);
export const FloatingPanelTitle = ({
className,
...props
}: FloatingPanelPrimitive.TitleProps) => (
<FloatingPanelPrimitive.Title
className={cn(
"flex min-w-0 flex-1 items-center gap-2 truncate font-semibold text-foreground text-sm leading-tight",
className,
)}
data-slot="floating-panel-title"
{...props}
/>
);
export const FloatingPanelControl = ({
className,
...props
}: FloatingPanelPrimitive.ControlProps) => (
<FloatingPanelPrimitive.Control
className={cn("flex shrink-0 items-center gap-1", className)}
data-no-drag=""
data-slot="floating-panel-control"
{...props}
/>
);
export const FloatingPanelStageTrigger = ({
...props
}: FloatingPanelPrimitive.StageTriggerProps) => (
<FloatingPanelPrimitive.StageTrigger
data-slot="floating-panel-stage-trigger"
{...props}
/>
);
export const FloatingPanelCloseTrigger = ({
...props
}: FloatingPanelPrimitive.CloseTriggerProps) => (
<FloatingPanelPrimitive.CloseTrigger
data-slot="floating-panel-close-trigger"
{...props}
/>
);
export const FloatingPanelBody = ({
className,
...props
}: FloatingPanelPrimitive.BodyProps) => (
<FloatingPanelPrimitive.Body
className={cn(
"min-h-0 flex-1 overflow-auto px-4 py-4 text-muted-foreground text-sm leading-relaxed",
className,
)}
data-slot="floating-panel-body"
{...props}
/>
);
/**
* Resize hit areas only — no Button chrome. Cursor comes from the machine inline styles.
* Slight hover tint on edges for discoverability.
*/
export const FloatingPanelResizeTrigger = ({
className,
...props
}: FloatingPanelPrimitive.ResizeTriggerProps) => (
<FloatingPanelPrimitive.ResizeTrigger
className={cn(
"z-20 border-0 bg-transparent p-0 shadow-none ring-0 outline-none",
"transition-colors duration-150",
"hover:bg-foreground/6 active:bg-foreground/8",
"data-disabled:pointer-events-none data-disabled:opacity-0",
"data-[axis=n]:h-2 data-[axis=n]:max-w-[calc(100%-1rem)]",
"data-[axis=s]:h-2 data-[axis=s]:max-w-[calc(100%-1rem)]",
"data-[axis=e]:w-2 data-[axis=e]:max-h-[calc(100%-1rem)]",
"data-[axis=w]:w-2 data-[axis=w]:max-h-[calc(100%-1rem)]",
"data-[axis=ne]:size-3 data-[axis=nw]:size-3 data-[axis=se]:size-3 data-[axis=sw]:size-3",
className,
)}
data-slot="floating-panel-resize-trigger"
{...props}
/>
);
export const FloatingPanelContext = FloatingPanelPrimitive.Context;
export const FloatingPanelRootProvider = (
props: FloatingPanelPrimitive.RootProviderProps,
) => <FloatingPanelPrimitive.RootProvider {...props} />;
export { useFloatingPanelContext };
export const FloatingPanelPositioner = ({
className,
...props
}: FloatingPanelPrimitive.PositionerProps) => (
<FloatingPanelPrimitive.Positioner
className={cn(className)}
data-slot="floating-panel-positioner-raw"
{...props}
/>
);
export const FloatingPanelContent = ({
className,
...props
}: FloatingPanelPrimitive.ContentProps) => (
<FloatingPanelPrimitive.Content
className={cn(className)}
data-slot="floating-panel-content-raw"
{...props}
/>
);
Update import aliases to match your project setup.
Usage
import * as FloatingPanel from "@/components/ui/floating-panel"Read exported parts in src/components/ui/floating-panel.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Basic
Floating panel
Drag the header, resize from edges, or use the window controls.
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import type { FloatingPanelProps } from "@/components/ui/floating-panel";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelDemoLayout = ({
title,
children,
...root
}: FloatingPanelProps & { title: string; children: React.ReactNode }) => (
<FloatingPanel defaultSize={{ width: 360, height: 220 }} {...root}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
{title}
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>{children}</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
const FloatingPanelBasicDemo = () => (
<FloatingPanelDemoLayout title="Floating panel">
<p>Drag the header, resize from edges, or use the window controls.</p>
</FloatingPanelDemoLayout>
);
export default FloatingPanelBasicDemo;
Context
Panel is closed.
Context
Status above comes from FloatingPanelContext.
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelContext,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelContextDemo = () => (
<FloatingPanel defaultSize={{ height: 220, width: 360 }}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelContext>
{(fp) => (
<p className="text-muted-foreground text-xs">
Panel is{" "}
<span className="font-medium text-foreground">
{fp.open ? "open" : "closed"}
</span>
.
</p>
)}
</FloatingPanelContext>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
Context
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>
<p>
Status above comes from{" "}
<code className="rounded bg-muted px-1 py-px">
FloatingPanelContext
</code>
.
</p>
</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
export default FloatingPanelContextDemo;
Controlled Open
Controlled open
open is driven by React state.
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelControlledOpenDemo = () => {
const [open, setOpen] = useState(false);
return (
<div className="flex flex-wrap items-center gap-2">
<Button
size="sm"
type="button"
variant="secondary"
onClick={() => {
setOpen(true);
}}
>
Open externally
</Button>
<FloatingPanel
defaultSize={{ height: 220, width: 360 }}
onOpenChange={(e) => {
setOpen(e.open);
}}
open={open}
>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Toggle panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
Controlled open
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>
<p>open is driven by React state.</p>
</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
</div>
);
};
export default FloatingPanelControlledOpenDemo;
Controlled Position
Controlled position
x: 200 · y: 200
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import type { FloatingPanelProps } from "@/components/ui/floating-panel";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelDemoLayout = ({
title,
children,
...root
}: FloatingPanelProps & { title: string; children: React.ReactNode }) => (
<FloatingPanel defaultSize={{ width: 360, height: 220 }} {...root}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
{title}
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>{children}</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
const FloatingPanelControlledPositionDemo = () => {
const [position, setPosition] = useState({ x: 200, y: 200 });
return (
<FloatingPanelDemoLayout
title="Controlled position"
onPositionChange={(e) => {
setPosition(e.position);
}}
position={position}
>
<p className="font-mono text-xs">
x: {Math.round(position.x)} · y: {Math.round(position.y)}
</p>
</FloatingPanelDemoLayout>
);
};
export default FloatingPanelControlledPositionDemo;
Controlled Size
Controlled size
width: 400 · height: 300
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import type { FloatingPanelProps } from "@/components/ui/floating-panel";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelDemoLayout = ({
title,
children,
...root
}: FloatingPanelProps & { title: string; children: React.ReactNode }) => (
<FloatingPanel defaultSize={{ width: 360, height: 220 }} {...root}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
{title}
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>{children}</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
const FloatingPanelControlledSizeDemo = () => {
const [size, setSize] = useState({ height: 300, width: 400 });
return (
<FloatingPanelDemoLayout
title="Controlled size"
onSizeChange={(e) => {
setSize(e.size);
}}
size={size}
>
<p className="font-mono text-xs">
width: {Math.round(size.width)} · height: {Math.round(size.height)}
</p>
</FloatingPanelDemoLayout>
);
};
export default FloatingPanelControlledSizeDemo;
Floating panel
Drag the header, resize from edges, or use the window controls.
import {
ArrowDownLeftIcon,
GripVerticalIcon,
Maximize2Icon,
MinusIcon,
XIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import type { FloatingPanelProps } from "@/components/ui/floating-panel";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelControl,
FloatingPanelDragTrigger,
FloatingPanelHeader,
FloatingPanelPopup,
FloatingPanelStageTrigger,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
const FloatingPanelDemoLayout = ({
title,
children,
...root
}: FloatingPanelProps & { title: string; children: React.ReactNode }) => (
<FloatingPanel defaultSize={{ width: 360, height: 220 }} {...root}>
<FloatingPanelTrigger asChild>
<Button size="sm" variant="outline">
Open panel
</Button>
</FloatingPanelTrigger>
<FloatingPanelPopup>
<FloatingPanelDragTrigger>
<FloatingPanelHeader>
<FloatingPanelTitle>
<GripVerticalIcon className="size-4 shrink-0 text-muted-foreground" />
{title}
</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelStageTrigger
aria-label="Minimize"
stage="minimized"
asChild
>
<Button variant="ghost" size="icon-sm">
<MinusIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Maximize"
stage="maximized"
asChild
>
<Button variant="ghost" size="icon-sm">
<Maximize2Icon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelStageTrigger
aria-label="Restore"
stage="default"
asChild
>
<Button variant="ghost" size="icon-sm">
<ArrowDownLeftIcon />
</Button>
</FloatingPanelStageTrigger>
<FloatingPanelCloseTrigger aria-label="Close" asChild>
<Button variant="ghost" size="icon-sm">
<XIcon />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
</FloatingPanelDragTrigger>
<FloatingPanelBody>{children}</FloatingPanelBody>
</FloatingPanelPopup>
</FloatingPanel>
);
const FloatingPanelBasicDemo = () => (
<FloatingPanelDemoLayout title="Floating panel">
<p>Drag the header, resize from edges, or use the window controls.</p>
</FloatingPanelDemoLayout>
);
export default FloatingPanelBasicDemo;
API reference
This component mirrors the upstream Ark UI primitive. All props and DOM behavior are defined by Ark unless you see an ark-cn-only row below.
FloatingPanelPopup
| Prop | Type | Description |
|---|---|---|
| disablePortal? | boolean | Renders content in-place instead of portaled. |
| positionerClassName? | string | Extra class on the positioner wrapper. |
See the ARK UI documentation for the full API.