Control Center Panel
A grid of glass control modules; long-press any tile and it springs open in place into a fine-grained control card (slider, segmented toggle) while the background blurs and recedes, springing back on release.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add control-center-panel.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ControlCenterPanel/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/control-center-panel.en.jsonOr, once registries are configured in components.json, install it as @webberui/control-center-panel.
Usage
import {
ControlCenterPanel,
ControlTile,
} from "@/components/ui/control-center-panel";
const [brightness, setBrightness] = React.useState(72);
const [appearance, setAppearance] = React.useState("auto");
<ControlCenterPanel columns={2}>
<ControlTile
id="brightness"
label="Brightness"
icon={<Sun />}
value={brightness}
onValueChange={setBrightness}
formatValue={(v) => `${v}%`}
/>
<ControlTile
id="appearance"
label="Appearance"
icon={<Palette />}
control="segmented"
value={appearance}
onValueChange={setAppearance}
options={[
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
{ label: "Auto", value: "auto" },
]}
/>
</ControlCenterPanel>;Props
ControlCenterPanel
| Prop | Type | Default | Description |
|---|---|---|---|
columns | number | 2 | Number of grid columns (2–4) |
value | string | null | — | Controlled mode: the id of the currently expanded tile (null means all collapsed) |
defaultValue | string | null | null | Initially expanded id in uncontrolled mode |
onValueChange | (id: string | null) => void | — | Fires when a tile expands or collapses |
className | string | — | Overrides the panel container's styles |
children | ReactNode | — | Put ControlTile children here |
ControlTile (shared)
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique tile identifier; the panel uses it to coordinate "only one expanded at a time" |
label | string | — | Title (such as "Brightness") |
icon | ReactNode | — | Icon in the top-left corner; a lucide-react icon is recommended |
control | 'slider' | 'segmented' | 'slider' | Control type |
onValueChange | (value) => void | — | Fires when the value or selection changes |
className | string | — | Overrides the tile's styles |
ControlTile — control="slider"
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Current value (controlled) |
min | number | 0 | Minimum |
max | number | 100 | Maximum |
step | number | 1 | Step |
formatValue | (v: number) => string | (v) => `${v}` | Formatting for the compact readout and the expanded card |
ControlTile — control="segmented"
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Currently selected value (controlled) |
options | { label: ReactNode; value: string }[] | — | Segment options, laid out at equal widths |
How it works
- Long-press to expand: the tile only expands after the pointer has been held for about 380ms; while held, the tile shrinks slightly to build tension, and releasing early springs it back into place (without expanding)
- Morphing in place: the tile and the expanded card share a
layoutId, so a decelerating spring grows the tile seamlessly from its grid cell into the fine-grained control card; only one tile is expanded at a time - Background recedes: while expanded, the whole grid is covered by a translucent overlay with
backdrop-blur, creating a layer where the background steps back and focus moves forward - Fine-grained slider: the slider inside the expanded card supports pointer dragging and the keyboard (arrow keys ±
step,PageUp/Down±10%,Home/Endto the ends), with the fill and thumb following on springs - Segmented toggle: equal-width options, with the highlight sliding to a position computed from the index (avoiding nested layout animations), and arrow keys cycling through them
- Collapse: click the background overlay or press
Escto collapse; the expanded card morphs back to its grid cell on the same spring
Accessibility
- Tiles are
role="button"witharia-haspopup="dialog"andaria-expanded;Enter/Spaceexpand directly (no long press needed for keyboard users) - The expanded card is a
role="dialog"witharia-modal; on open, focus moves into the card and the grid getsinertto leave the tab order, and on collapse focus returns to the original tile - The slider is a
role="slider"witharia-valuemin/max/nowandaria-valuetext; the segmented control is arole="radiogroup"/role="radio"marked witharia-checked - When the user has "reduce motion" enabled at the system level: the long-press expansion and every control feature are kept exactly as they are, and only the morph offsets and the tension scaling are removed, replaced by a brief fade in / fade out
Slide Over Panel
A non-modal narrow floating panel that can be dragged and magnetically snapped to either side, and flicked away to collapse into an edge handle — springs tracking the pointer throughout.
Now Playing Sheet
A persistent mini player bar at the bottom; tap it and the artwork scales up as a shared element while the whole layer springs open into a full-screen now-playing page, dismissed by a downward drag whose gesture velocity decides the outcome.