Grid Systems

Widget Board

An iOS home-screen widget grid — frosted glass cards in sm/md/lg size tiers, an edit mode where everything wiggles and can be removed, and a Smart Stack that pages on a timer.

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 widget-board.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.

4
Active edited usageDefault props
<WidgetBoard/>

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/widget-board.en.json

Installing also writes the --wb-duration-fast and --wb-ease-out CSS variables into your global stylesheet.

Usage

import { WidgetBoard, Widget, WidgetStack } from "@/components/ui/widget-board";

const [editing, setEditing] = React.useState(false);

<WidgetBoard editing={editing} onEditingChange={setEditing}>
  <Widget id="clock" size="sm" label="Clock">
    <Clock />
  </Widget>
  <Widget id="weather" size="md" label="Weather">
    <Weather />
  </Widget>
  <Widget id="music" size="lg" label="Music">
    <MusicPlayer />
  </Widget>
  <WidgetStack id="glance" size="sm" label="Smart Stack" interval={4}>
    <CalendarGlance />
    <Reminders />
  </WidgetStack>
</WidgetBoard>

Props

WidgetBoard

PropTypeDefaultDescription
editingboolean—Controlled edit-mode state; when omitted, the component manages it internally
onEditingChange(editing: boolean) => void—Fires when edit mode changes (including when clicking empty grid space or pressing Esc ends editing)
childrenReact.ReactNode—Put Widget / WidgetStack children here directly
classNamestring—Appended to the board container's className (the container is also the container-query reference, so avoid adding padding here)

Widget

PropTypeDefaultDescription
idstring— (required)Unique key: used to pair removal / re-adding and to compute the wiggle phase
size'sm' | 'md' | 'lg''sm'Size tier: sm 1×1, md 2×1, lg 2×2
labelstringidDisplay name: used for the remove button's aria-label and for the "removed" tray chip
childrenReact.ReactNode—Card content: no padding by default, so you can go full-bleed with an image or add padding yourself
classNamestring—Appended to a single widget's frame className

WidgetStack

PropTypeDefaultDescription
idstring— (required)Unique key: used to pair removal / re-adding and to compute the wiggle phase
size'sm' | 'md' | 'lg''sm'Size tier: sm 1×1, md 2×1, lg 2×2
intervalnumber4Interval between automatic page turns (seconds)
labelstringidDisplay name: used for the remove button's aria-label and for the "removed" tray chip
childrenReact.ReactNode—Several content nodes; each direct child is one page
classNamestring—Appended to the stack frame's className

How it works

  • Square cells: 4 columns when the container is ≥ 24rem wide and 2 otherwise; the row height is derived from the column width with a container query (100cqw), so the cells are always 1:1 squares and md and lg map naturally onto iOS's 2×1 and 2×2 proportions. grid-flow-row-dense lets the smaller widgets that follow fill in the gaps that md/lg leave behind
  • Wiggle: in edit mode, CSS keyframes loop rotate ±1.2deg infinitely; each widget hashes its id into a negative animation-delay to stagger the phase, so the board does not wobble in unison. The phase comes from a hash rather than Math.random(), so SSR and client output match
  • Layer separation: the outer motion.div handles the FLIP reflow and the shrink-out exit, the middle layer handles the fly-back animation on re-adding, and the inner layer runs the CSS wiggle, so the three transforms never interfere with each other
  • Removal and reflow: in edit mode a "−" button floats up in the top-left corner (a native button whose aria-label is "remove" plus the label); clicking it has AnimatePresence popLayout shrink and fade that widget out in place while the rest reflow into position with a spring FLIP
  • Removed tray: removed widgets are listed along the bottom of the board and clicking a chip adds one back; at the moment of the click the center distance between the chip and the new cell is measured, and the widget scales up and flies from the tray back into the grid
  • Smart Stack: pages slide vertically every interval seconds, with small dots along the right edge marking the current page; the timer is cleaned up on unmount, and the rotation pauses automatically in edit mode
  • Ending edit mode: clicking empty grid space or pressing Esc ends edit mode, notifying the outside through onEditingChange in controlled mode
  • Reduce motion: when the user has "reduce motion" enabled at the system level, the wiggle is disabled (belt and braces, through both JS and the CSS prefers-reduced-motion), the FLIP and the flight are disabled, and the Smart Stack's rotation switches instantly — the functionality is entirely unaffected
  • Frosted glass cards: backdrop-blur adapts to light and dark mode, and the effect looks best over a colored "wallpaper" background