shadcn.io is not affiliated with official shadcn/ui
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.jsonThis component depends on Button, Preview Frame and Scroll Area. Install them first if you haven't already.
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
FloatingPanelTrigger
FloatingPanelContent
└── FloatingPanelContent
FloatingPanelHeader
FloatingPanelBody
FloatingPanelTitle
FloatingPanelResizeTrigger
FloatingPanelDragTrigger
FloatingPanelStageTrigger
FloatingPanelCloseTrigger
FloatingPanelControlUsage
import { createListCollection } from "@ark-ui/react/collection";
import { Settings2Icon, XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import {
FloatingPanel,
FloatingPanelBody,
FloatingPanelCloseTrigger,
FloatingPanelContent,
FloatingPanelControl,
FloatingPanelFooter,
FloatingPanelHeader,
FloatingPanelMaximize,
FloatingPanelMinimize,
FloatingPanelRestore,
FloatingPanelTitle,
FloatingPanelTrigger,
} from "@/components/ui/floating-panel";
import {
NumberInput,
NumberInputDecrement,
NumberInputGroup,
NumberInputIncrement,
NumberInputInput,
} from "@/components/ui/number-input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";const FloatingPanelDemo = () => (
<FloatingPanel defaultSize={{ width: 360, height: 300 }}>
<FloatingPanelTrigger asChild>
<Button variant="outline">Open</Button>
</FloatingPanelTrigger>
<FloatingPanelContent>
<FloatingPanelHeader>
<Settings2Icon />
<FloatingPanelTitle>Settings</FloatingPanelTitle>
<FloatingPanelControl>
<FloatingPanelMinimize />
<FloatingPanelMaximize />
<FloatingPanelRestore />
<FloatingPanelCloseTrigger asChild>
<Button aria-label="Close" size="icon-xs">
<XIcon aria-hidden />
</Button>
</FloatingPanelCloseTrigger>
</FloatingPanelControl>
</FloatingPanelHeader>
<FloatingPanelBody>
<Field>
<FieldLabel>Font family</FieldLabel>
<Select collection={collection} defaultValue={["Inter"]}>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{collection.items.map((item) => (
<SelectItem item={item} key={item}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel>Font size</FieldLabel>
<NumberInput className="w-full" defaultValue="16">
<NumberInputGroup>
<NumberInputDecrement />
<NumberInputInput />
<NumberInputIncrement />
</NumberInputGroup>
</NumberInput>
</Field>
</FloatingPanelBody>
<FloatingPanelFooter>
<Button variant="outline">Save</Button>
</FloatingPanelFooter>
</FloatingPanelContent>
</FloatingPanel>
);
const collection = createListCollection({
items: ["Inter", "Roboto", "Helvetica", "Geist"],
});Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
ArrowLeft | Move left. |
ArrowRight | Move right. |
ArrowUp | Move up. |
ArrowDown | Move down. |
Home | Go to the start. |
End | Go to the end. |
Demos
Controlled Position
Controlled Size
Custom Spacing
API Reference
FloatingPanel
PropType
AttributeType
FloatingPanelTrigger
button
PropType
AttributeType
FloatingPanelContent
div
PropType
AttributeType
CSS variableType
FloatingPanelContent
div
PropType
AttributeType
CSS variableType
FloatingPanelHeader
div
PropType
AttributeType
CSS variableType
FloatingPanelBody
div
PropType
AttributeType
CSS variableType
FloatingPanelTitle
h2
PropType
AttributeType
FloatingPanelResizeTrigger
div
PropType
AttributeType
FloatingPanelDragTrigger
div
PropType
AttributeType
FloatingPanelStageTrigger
button
PropType
AttributeType
FloatingPanelCloseTrigger
button
PropType
AttributeType
FloatingPanelControl
div
PropType
AttributeType
FloatingPanelMinimize
button
PropType
AttributeType
FloatingPanelMaximize
button
PropType
AttributeType
FloatingPanelRestore
button
PropType
AttributeType
FloatingPanelFooter
div
PropType
AttributeType
CSS variableType
useFloatingPanel
PropType