KQ-1114 · CORNER/PIP

Corner PiP

A floating player inside its own frame that you throw into a corner: it follows the finger 1:1 and rubber-bands past the edges, and on release its momentum is projected with a deceleration set by friction, the corner whose quadrant the projected centre lands in is where it goes, and a landing spring that takes the release velocity carries it there, overshooting more the more slippery it is. A throw off a side edge tucks it into a tab you pull or press to bring back. The grip is a button whose arrow keys move it corner to corner and into the edge, with the same flight, swish and soft thud.

KQ-1114 · CORNER/PIP
LIVE
MAJO
Drag or throw it into a corner. On the grip, arrow keys move it corner to corner and into the side edge, and Enter moves it clockwise.

call · bottom right · fling it to a corner

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/corner-pip

Props

player
ReactNode
default —
What plays inside the floating player. Its own controls work as usual; a drag that starts on them still moves the player.
children
ReactNode
default —
The screen behind the player: whatever the frame shows.
label
string
default —
The player's name: its accessible label, and the names of its grip ("Move …") and tab ("Show …").
value / defaultValue
{ corner: "top-left" | "top-right" | "bottom-left" | "bottom-right"; tucked: boolean }
default { corner: "bottom-right", tucked: false }
Where the player sits: a corner, and whether it is tucked into that corner's side edge. Controlled or uncontrolled; a refused move flies back.
onValueChange
(value: CornerPipValue) => void
default —
Fires from the throw or the key that moved the player, with its new place.
friction
number
default 0.5
How quickly a throw dies, 0 to 1. Low friction coasts further and overshoots the corner; high friction needs a hard fling and settles flat.
inset
number
default 12
The corner margin in px, 0 to 24.
tuck
boolean
default true
A throw whose projection leaves through a side edge tucks the player into that edge, leaving a 22 px tab.
size
"sm" | "md" | "lg"
default "md"
The player's width: 112, 144 or 176 px at 16:9, never more than 44% of the frame.
sound
boolean
default false
A swish on a fast throw (higher and louder the faster) and a soft thud on the frame it lands. Nothing plays, and no audio context exists, unless this is on.
disabled
boolean
default false
Dims the frame and stops every drag and key.
className
string
default —
Merged onto the frame's classes; the frame is 2:1 unless an aspect class here says otherwise.