App Shells & Panels

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

2
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
columnsnumber2Number of grid columns (2–4)
valuestring | null—Controlled mode: the id of the currently expanded tile (null means all collapsed)
defaultValuestring | nullnullInitially expanded id in uncontrolled mode
onValueChange(id: string | null) => void—Fires when a tile expands or collapses
classNamestring—Overrides the panel container's styles
childrenReactNode—Put ControlTile children here

ControlTile (shared)

PropTypeDefaultDescription
idstring—Unique tile identifier; the panel uses it to coordinate "only one expanded at a time"
labelstring—Title (such as "Brightness")
iconReactNode—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
classNamestring—Overrides the tile's styles

ControlTile — control="slider"

PropTypeDefaultDescription
valuenumber—Current value (controlled)
minnumber0Minimum
maxnumber100Maximum
stepnumber1Step
formatValue(v: number) => string(v) => `${v}`Formatting for the compact readout and the expanded card

ControlTile — control="segmented"

PropTypeDefaultDescription
valuestring—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/End to 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 Esc to collapse; the expanded card morphs back to its grid cell on the same spring

Accessibility

  • Tiles are role="button" with aria-haspopup="dialog" and aria-expanded; Enter / Space expand directly (no long press needed for keyboard users)
  • The expanded card is a role="dialog" with aria-modal; on open, focus moves into the card and the grid gets inert to leave the tab order, and on collapse focus returns to the original tile
  • The slider is a role="slider" with aria-valuemin/max/now and aria-valuetext; the segmented control is a role="radiogroup" / role="radio" marked with aria-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