Lasso Select Stage
Drag a lasso box across a grid; the items it catches lift and light up in sequence, and on release the count badge morphs into a floating batch action bar.
npx shadcn@latest add https://webberui.com/r/lasso-select-stage.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<LassoSelectStage />
Installation
npx shadcn@latest add https://webberui.com/r/lasso-select-stage.jsonOr, once registries are configured in components.json, install it as @webberui/lasso-select-stage.
Usage
import {
LassoSelectStage,
LassoItem,
} from "@/components/ui/lasso-select-stage";
export function Example() {
return (
<LassoSelectStage
columns={4}
actions={[
{ id: "share", label: "Share" },
{ id: "delete", label: "Delete", destructive: true },
]}
onSelectionChange={(ids) => console.log(ids)}
>
{items.map((item) => (
<LassoItem key={item.id} id={item.id} label={item.name}>
{/* any card content */}
</LassoItem>
))}
</LassoSelectStage>
);
}Press and drag anywhere on the stage to pull out a selection box; the LassoItems it catches lift and light up one after another. On release, the selection count badge morphs into a floating batch action bar; pressing the ✕ on that bar cancels, and the bar shatters into small dots that scatter back to each item. A single item can also be toggled by clicking it or with the keyboard (Enter / Space).
LassoSelectStage Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Put LassoItem children here |
columns | number | 4 | Number of grid columns |
gap | number | 12 | Gap between cells (px) |
actions | LassoAction[] | [] | Actions on the floating batch bar |
selectedIds | string[] | — | Controlled array of selected ids |
defaultSelectedIds | string[] | [] | Initial selected ids in uncontrolled mode |
onSelectionChange | (ids: string[]) => void | — | Fires when the selection changes |
aria-label | string | "可框選項目舞台" | Accessible label for the stage |
LassoItem Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique key, used to pair selection and the scatter-back animation |
label | string | id | Accessible label |
children | ReactNode | — | Card content |
LassoAction
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the action |
label | string | Button text |
icon | ReactNode | Optional icon, placed before the text |
onSelect | (ids: string[]) => void | Fires on click with the current selection; the selection clears automatically afterwards |
destructive | boolean | Whether this is a destructive action (red accent) |
How it works
- Controlled and uncontrolled modes: passing
selectedIdshands control to the outside; otherwise the component starts fromdefaultSelectedIdsand maintains its own state. - Lasso detection: a drag only counts as a lasso once it travels more than 4px; a press without movement toggles a single item, and pressing on empty space clears the selection.
- Sequential lift: the stagger delay is derived from each item's order in the grid, so the highlight rings light up one by one as the lasso sweeps.
- Morphing aggregation: the in-drag count badge and the post-release action bar share a
layoutId, so they morph smoothly into one another rather than cross-fading. - Scatter-back animation: on cancel, the action bar shatters into small dots that fly back along a trajectory to each item's original position.
Accessibility
- The stage is
role="listbox" aria-multiselectable, and items arerole="option"witharia-selected. - Items are keyboard-focusable; Enter / Space toggles selection and
Escclears the whole selection. - The floating action bar is
role="toolbar", and the selection count is announced through anaria-liveregion. - When the user has "reduce motion" enabled at the system level, the lift, morphing aggregation, and scatter-back animations are all disabled; only the instant state change remains.
Filter Composer Bar
A Linear-style filter composer — pick a field from "Add filter" and the pill chip springs in; the chip's value opens a menu in place, and removing one lets the rest close ranks with FLIP.
Focus Recession Stage
A page-level focus system — when any region enters focus/edit mode, the rest of the layout recedes as a whole: scaled down, dimmed, and blurred into a depth-of-field backdrop, springing back on Esc or an outside click.