Shadcn Floating Panel for React and Tailwind
Displays content in a floating window.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/floating-panel.jsonpnpm dlx shadcn@latest add https://kit.dev/r/floating-panel.jsonnpx shadcn@latest add https://kit.dev/r/floating-panel.jsonyarn shadcn@latest add https://kit.dev/r/floating-panel.json<Step>This component depends on Button and Scroll Area. Install them first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/react lucide-reactpnpm add @ark-ui/react lucide-reactnpm install @ark-ui/react lucide-reactyarn add @ark-ui/react lucide-reactCopy and paste the following code into your project.
"use client";
import { ark } from "@ark-ui/react/factory";
import {
FloatingPanel as ArkFloatingPanel,
useFloatingPanelContext,
} from "@ark-ui/react/floating-panel";
import { Portal } from "@ark-ui/react/portal";
import { Maximize, MaximizeIcon, MinimizeIcon, MinusIcon } from "lucide-react";
import type React from "react";
import { cn } from "@/lib/utils";
import { Button, type ButtonProps } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
usePreviewFrame,
usePreviewPortal,
} from "@/registry/react/lib/preview-frame";
export const useFloatingPanel = useFloatingPanelContext;
export const FloatingPanel = (
props: React.ComponentProps<typeof ArkFloatingPanel.Root>
) => {
const {
allowOverflow,
getBoundaryEl,
lazyMount = true,
unmountOnExit = true,
...rest
} = props;
const inPreview = usePreviewFrame();
const portalNode = usePreviewPortal();
return (
<ArkFloatingPanel.Root
allowOverflow={allowOverflow ?? !inPreview}
data-slot="floating-panel"
getBoundaryEl={
getBoundaryEl ?? (inPreview ? () => portalNode : undefined)
}
lazyMount={lazyMount}
unmountOnExit={unmountOnExit}
{...rest}
/>
);
};
export const FloatingPanelTrigger = (
props: React.ComponentProps<typeof ArkFloatingPanel.Trigger>
) => <ArkFloatingPanel.Trigger data-slot="floating-panel-trigger" {...props} />;
interface FloatingPanelContentProps
extends React.ComponentProps<typeof ArkFloatingPanel.Content> {
/**
* Enable resizable panel
*
* @default true
*/
resizable?: boolean;
}
export const FloatingPanelContent = (props: FloatingPanelContentProps) => {
const { resizable = true, className, children, ...rest } = props;
return (
<Portal>
<ArkFloatingPanel.Positioner
className="inset-s-(--x) top-(--y) z-50"
data-slot="floating-panel-positioner"
>
<ArkFloatingPanel.Content
className={cn(
"[--space:--spacing(4)]",
"group/floating-panel",
"relative",
"flex flex-col",
"h-(--height) min-h-0 w-(--width)",
"bg-popover",
"text-popover-foreground",
"rounded-2xl border shadow-lg/5",
"transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform",
"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-[98%] data-[state=open]:animate-in",
"motion-reduce:animate-none! motion-reduce:transition-none!",
className
)}
data-slot="floating-panel-content"
{...rest}
>
{children}
{resizable && (
<>
<FloatingPanelResizeTrigger axis="n" />
<FloatingPanelResizeTrigger axis="e" />
<FloatingPanelResizeTrigger axis="w" />
<FloatingPanelResizeTrigger axis="s" />
<FloatingPanelResizeTrigger axis="ne" />
<FloatingPanelResizeTrigger axis="se" />
<FloatingPanelResizeTrigger axis="sw" />
<FloatingPanelResizeTrigger axis="nw" />
</>
)}
</ArkFloatingPanel.Content>
</ArkFloatingPanel.Positioner>
</Portal>
);
};
export const FloatingPanelDragTrigger = (
props: React.ComponentProps<typeof ArkFloatingPanel.DragTrigger>
) => (
<ArkFloatingPanel.DragTrigger
data-slot="floating-panel-drag-trigger"
{...props}
/>
);
export const FloatingPanelHeader = (
props: React.ComponentProps<typeof ArkFloatingPanel.Header>
) => {
const { className, ...rest } = props;
return (
<FloatingPanelDragTrigger>
<ArkFloatingPanel.Header
className={cn(
"relative",
"min-w-0",
"px-(--space) py-[calc(var(--space)*0.5)]",
"flex flex-1 shrink-0 items-center gap-2",
"bg-muted/48",
"rounded-t-2xl border-b",
"overflow-hidden",
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...rest}
/>
</FloatingPanelDragTrigger>
);
};
export const FloatingPanelControl = (
props: React.ComponentProps<typeof ArkFloatingPanel.Control>
) => {
const { className, ...rest } = props;
return (
<ArkFloatingPanel.Control
className={cn("ms-auto flex items-center gap-2 rtl:me-auto", className)}
{...rest}
/>
);
};
interface FloatingPanelStageTriggerProps
extends Omit<
React.ComponentProps<typeof ArkFloatingPanel.StageTrigger>,
"stage"
>,
ButtonProps {}
export const FloatingPanelMinimize = (
props: FloatingPanelStageTriggerProps
) => {
const { size = "icon-xs", variant = "ghost", ...rest } = props;
return (
<ArkFloatingPanel.StageTrigger {...rest} asChild stage="minimized">
<Button aria-label="Minimize" size={size} variant={variant}>
<MinusIcon />
</Button>
</ArkFloatingPanel.StageTrigger>
);
};
export const FloatingPanelMaximize = (
props: FloatingPanelStageTriggerProps
) => {
const { size = "icon-xs", variant = "ghost", ...rest } = props;
return (
<ArkFloatingPanel.StageTrigger {...rest} asChild stage="maximized">
<Button aria-label="Maximize" size={size} variant={variant}>
<Maximize />
</Button>
</ArkFloatingPanel.StageTrigger>
);
};
export const FloatingPanelRestore = (props: FloatingPanelStageTriggerProps) => {
const { size = "icon-xs", variant = "outline", ...rest } = props;
return (
<ArkFloatingPanel.StageTrigger {...rest} asChild stage="default">
<Button aria-label="Restore" size={size} variant={variant}>
<MinimizeIcon className="hidden group-data-maximized/floating-panel:block" />
<MaximizeIcon className="hidden group-data-minimized/floating-panel:block" />
</Button>
</ArkFloatingPanel.StageTrigger>
);
};
export const FloatingPanelTitle = (
props: React.ComponentProps<typeof ArkFloatingPanel.Title>
) => {
const { className, ...rest } = props;
return (
<ArkFloatingPanel.Title
className={cn(
"min-w-0 flex-1",
"flex items-center gap-2",
"truncate whitespace-nowrap font-medium text-sm leading-none",
className
)}
data-slot="floating-panel-title"
{...rest}
/>
);
};
export const FloatingPanelResizeTrigger = (
props: React.ComponentProps<typeof ArkFloatingPanel.ResizeTrigger>
) => (
<ArkFloatingPanel.ResizeTrigger
data-slot="floating-panel-resize-trigger"
{...props}
/>
);
export const FloatingPanelStageTrigger = (
props: React.ComponentProps<typeof ArkFloatingPanel.StageTrigger>
) => (
<ArkFloatingPanel.StageTrigger
data-slot="floating-panel-stage-trigger"
{...props}
/>
);
export const FloatingPanelCloseTrigger = (
props: React.ComponentProps<typeof ArkFloatingPanel.CloseTrigger>
) => (
<ArkFloatingPanel.CloseTrigger
data-slot="floating-panel-close-trigger"
{...props}
/>
);
interface FloatingPanelBodyProps
extends React.ComponentProps<typeof ArkFloatingPanel.Body> {
/**
* Add a fade effect to the scroll area
*
* @default false
*/
scrollFade?: boolean;
}
export const FloatingPanelBody = (props: FloatingPanelBodyProps) => {
const { scrollFade = false, className, children, ...rest } = props;
return (
<ScrollArea scrollFade={scrollFade}>
<ArkFloatingPanel.Body
className={cn(
"flex flex-col gap-4",
"p-(--space)",
"overflow-auto",
"in-[[data-slot=floating-panel-content]:has([data-slot=floating-panel-footer]:not(.border-t))]:pb-1",
className
)}
data-slot="floating-panel-body"
{...rest}
>
{children}
</ArkFloatingPanel.Body>
</ScrollArea>
);
};
export const FloatingPanelFooter = (
props: React.ComponentProps<typeof ark.div>
) => {
const { className, ...rest } = props;
return (
<ark.div
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
"sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
"px-(--space) py-4",
"bg-muted/48",
"border-t",
className
)}
data-slot="floating-panel-footer"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
FloatingPanel
├── FloatingPanelTrigger
└── FloatingPanelContent
├── FloatingPanelDragTrigger
├── FloatingPanelHeader
│ ├── FloatingPanelControl
│ │ ├── FloatingPanelMinimize
│ │ ├── FloatingPanelMaximize
│ │ └── FloatingPanelRestore
│ ├── FloatingPanelTitle
│ ├── FloatingPanelResizeTrigger
│ ├── FloatingPanelStageTrigger
│ └── FloatingPanelCloseTrigger
├── FloatingPanelBody
└── FloatingPanelFooterUsage
import {
FloatingPanel,
FloatingPanelTrigger,
FloatingPanelContent,
FloatingPanelHeader,
FloatingPanelTitle,
FloatingPanelBody,
} from "@/components/ui/floating-panel";<FloatingPanel>
<FloatingPanelTrigger />
<FloatingPanelContent>
<FloatingPanelHeader>
<FloatingPanelTitle />
</FloatingPanelHeader>
<FloatingPanelBody />
</FloatingPanelContent>
</FloatingPanel>Examples
Controlled Size
Control the panel size programmatically using the size and onSizeChange props.
Controlled Position
Control the panel position programmatically using the position and onPositionChange props.
Custom spacing
Use [--space:--spacing("value")] on FloatingPanelContent to adjust internal padding.
Default spacing is --spacing(4).
You can use breakpoint utilities to change the internal spacing at different screen sizes.
md:[--space:--spacing(6)] lg:[--space:--spacing(8)]API Reference
FloatingPanel
Root component. Manages panel position and drag state.
| Prop | Type | Default |
|---|---|---|
open | boolean | - |
defaultOpen | boolean | false |
onOpenChange | (details: OpenChangeDetails) => void | - |
position | Point | - |
defaultPosition | Point | - |
onPositionChange | (details: PositionChangeDetails) => void | - |
size | Size | - |
defaultSize | Size | - |
onSizeChange | (details: SizeChangeDetails) => void | - |
lazyMount | boolean | true |
unmountOnExit | boolean | true |
closeOnEscape | boolean | true |
draggable | boolean | true |
resizable | boolean | true |
className | string | - |
FloatingPanelTrigger
Opens the floating panel on click.
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
FloatingPanelContent
Holds the panel content.
| Prop | Type | Default |
|---|---|---|
resizable | boolean | true |
className | string | - |
| Attribute | Default |
|---|---|
--space | --spacing(4) |
FloatingPanelHeader
Header area. Acts as drag handle for repositioning.
| Prop | Type | Default |
|---|---|---|
className | string | - |
FloatingPanelTitle
Title for the panel.
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
FloatingPanelBody
Scrollable body content. Uses ScrollArea for overflow.
| Prop | Type | Default |
|---|---|---|
scrollFade | boolean | false |
className | string | - |
FloatingPanelCloseTrigger
Closes the panel on click.
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
For a complete list of props, see the Ark UI documentation.