All components
Floating Panel
Draggable utility windows with a drag handle, title bar, and scrollable body.
Console
▲ build completed in 412ms
✓ 128 rules compiled
✓ 0 warnings
123456789101112131415161718192021222324252627import { FloatingPanel } from "#components/ui/ark-ui/floating-panel.tsx";import { Button } from "#components/ui/ark-ui/button.tsx";
export function Example() { return ( // dragging, resizing, and stage transitions come from Ark's machine; // Portal → Positioner is the overlay chain <FloatingPanel.Root defaultPosition={{ x: 120, y: 160 }} defaultSize={{ width: 280, height: 180 }}> <FloatingPanel.Trigger asChild> <Button variant="surface">Open panel</Button> </FloatingPanel.Trigger> <FloatingPanel.Portal> <FloatingPanel.Positioner> <FloatingPanel.Content> <FloatingPanel.Header> <FloatingPanel.DragTrigger> <FloatingPanel.Title>Console</FloatingPanel.Title> </FloatingPanel.DragTrigger> <FloatingPanel.CloseTrigger aria-label="Close">×</FloatingPanel.CloseTrigger> </FloatingPanel.Header> <FloatingPanel.Body>build completed in 412ms</FloatingPanel.Body> </FloatingPanel.Content> </FloatingPanel.Positioner> </FloatingPanel.Portal> </FloatingPanel.Root> );}Install
1npx stylexblocks add floating-panelSource
components/ui/ark-ui/floating-panel.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187import * as stylex from "@stylexjs/stylex";import { FloatingPanel as ArkFloatingPanel } from "@ark-ui/react/floating-panel";import { Portal } from "@ark-ui/react/portal";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { semanticRadii } from "@stylexblocks/ui/semantic-tokens/radii.stylex.ts";import { semanticShadows } from "@stylexblocks/ui/semantic-tokens/shadows.stylex.ts";import { durations } from '@stylexblocks/ui/tokens/durations.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'import { zIndices } from '@stylexblocks/ui/tokens/z-indices.stylex.ts'
const floatingPanelPositioner = stylex.create({ base: { '--floating-panel-z-index': zIndices.layer4, zIndex: 'calc(var(--floating-panel-z-index) + var(--layer-index, 0))', },})
const floatingPanelContent = stylex.create({ base: { display: 'flex', flexDirection: 'column', overflow: 'hidden', outline: 'none', position: 'relative', transformOrigin: 'var(--transform-origin)', animationName: { default: null, ':is([data-open], [data-state="open"])': 'scale-in, fade-in', ':is([data-closed], [data-state="closed"])': 'scale-out, fade-out', }, animationDuration: { default: null, ':is([data-open], [data-state="open"])': durations.fast, ':is([data-closed], [data-state="closed"])': durations.faster, }, },})
const floatingPanelDragTrigger = stylex.create({ base: { flex: 1, display: 'flex', alignItems: 'center', gap: spacing._2, minWidth: 0, },})
const floatingPanelHeader = stylex.create({ base: { display: 'flex', alignItems: 'center', gap: spacing._2, paddingInline: spacing._3, paddingBlock: spacing._2, borderBottomWidth: '1px', backgroundColor: semanticColors.bgSubtle, flex: 'none', },})
const floatingPanelTitle = stylex.create({ base: { fontWeight: fontWeights.medium, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', },})
const floatingPanelBody = stylex.create({ base: { flex: 1, overflow: 'auto', padding: spacing._3, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, },})
const floatingPanelResizeTrigger = stylex.create({ base: { zIndex: 1, '--size': sizes._2_5, },})
// Local chrome the recipe leaves to the composition layer: the window// surface, a drag cursor on the handle, an icon-button close trigger, and a// real truncation for the title (the generated `truncate` shorthand is not a// CSS property).const chrome = stylex.create({ content: { backgroundColor: semanticColors.bgPanel, borderWidth: "1px", borderStyle: "solid", borderColor: semanticColors.border, borderRadius: semanticRadii.panel, boxShadow: semanticShadows.overlay, }, dragTrigger: { cursor: "move", }, title: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }, closeTrigger: { display: "inline-flex", alignItems: "center", justifyContent: "center", width: "1.5rem", height: "1.5rem", borderRadius: semanticRadii.control, borderStyle: "none", color: semanticColors.fgMuted, backgroundColor: { default: "transparent", ":hover": semanticColors.bgMuted }, cursor: "pointer", },});
const floatingPanelRecipe = defineSlotRecipe({ slots: { positioner: { styles: floatingPanelPositioner }, content: { styles: floatingPanelContent }, header: { styles: floatingPanelHeader }, dragTrigger: { styles: floatingPanelDragTrigger }, title: { styles: floatingPanelTitle }, body: { styles: floatingPanelBody }, resizeTrigger: { styles: floatingPanelResizeTrigger }, }, defaultVariants: {},});
const { withContext } = createSlotRecipeContext(floatingPanelRecipe);
/** * Floating Panel — Ark's floating-panel machine wearing the floating-panel * slot recipe: real dragging, resizing, stage transitions (minimize / * maximize), and fixed-position management all come from the machine. * Portal stays an unstyled engine part; Positioner is a recipe slot. */export const FloatingPanel = { Root: ArkFloatingPanel.Root, Trigger: ArkFloatingPanel.Trigger, Context: ArkFloatingPanel.Context, Portal, Positioner: withContext(ArkFloatingPanel.Positioner, "positioner", "FloatingPanelPositioner"), Content: withContext( ArkFloatingPanel.Content, () => [floatingPanelContent.base, chrome.content], "FloatingPanelContent", ), Header: withContext(ArkFloatingPanel.Header, "header", "FloatingPanelHeader"), DragTrigger: withContext( ArkFloatingPanel.DragTrigger, () => [floatingPanelDragTrigger.base, chrome.dragTrigger], "FloatingPanelDragTrigger", ), Title: withContext( ArkFloatingPanel.Title, () => [floatingPanelTitle.base, chrome.title], "FloatingPanelTitle", ), Body: withContext(ArkFloatingPanel.Body, "body", "FloatingPanelBody"), CloseTrigger: withContext( ArkFloatingPanel.CloseTrigger, () => [chrome.closeTrigger], "FloatingPanelCloseTrigger", ), ResizeTrigger: withContext( ArkFloatingPanel.ResizeTrigger, "resizeTrigger", "FloatingPanelResizeTrigger", ),};