Have more questions? Join our

Animation (boardweaver/motion)

The platform bundles Motion — the motion package, version 12.43.0, formerly Framer Motion — and exposes its React surface as boardweaver/motion. Like every bundled library it resolves to a single shared copy the frame provides, so nothing is added to your bundle and your game is version-locked to that copy.

import { motion, AnimatePresence } from "boardweaver/motion";

Board games are animation-heavy and hand-rolled CSS transitions are where generated code is weakest. Two primitives do most of the work, and they map directly onto what a board game needs.

layoutId — a piece moving from A to B

Give the same layoutId to an element in two places and Motion animates between them automatically, however the tree is restructured. This is "a card moves from hand to table" expressed in one prop — no measuring, no coordinates.

{hand.map((card) => (
  <motion.div key={card.id} layoutId={card.id} onClick={() => play(card.id)}>
    {card.name}
  </motion.div>
))}

Render the same layoutId inside the table area next render and the card flies there. Wrap groups that should animate together in <LayoutGroup>.

AnimatePresence — pieces entering and leaving

React removes an element the moment it leaves the tree, which is exactly wrong for a captured piece or a played card. AnimatePresence keeps it mounted until its exit animation finishes. This matters more than it looks: a broadcast can splice a piece out of the middle of the array, so pieces disappear without warning.

<AnimatePresence>
  {pieces.map((p) => (
    <motion.div
      key={p.pieceId}
      initial={{ scale: 0 }}
      animate={{ scale: 1 }}
      exit={{ scale: 0, opacity: 0 }}
    />
  ))}
</AnimatePresence>

key must be stable and unique or exit animations will not run.

Animating what an opponent did

useLastChange() (see the match API section) reports which piece and space ids changed in the last snapshot, whether it was your own move, and whether several actions were coalesced into one update. Combine it with layoutId and you get opponent moves animating for free.

The platform cannot tell you what an action meant — "a capture happened" is not derivable from a state diff. If you need that, have applyActions write a breadcrumb onto metaData and read it in the client.

Respect reduced motion

Some players get motion sick. This is not optional polish:

import { MotionConfig, useReducedMotion } from "boardweaver/motion";

// Whole-subtree: honours the player's OS setting.
<MotionConfig reducedMotion="user">…</MotionConfig>

// Or branch on it yourself.
const reduced = useReducedMotion();
<motion.div transition={{ duration: reduced ? 0 : 0.3 }} />

What is available

Import Use it for
motion The animated elements: motion.div, motion.button, motion.svg, …
AnimatePresence Exit animations for elements leaving the tree
LayoutGroup Coordinating layout animations across siblings
MotionConfig Subtree-wide defaults, including reducedMotion
useReducedMotion Branching on the player's motion preference
useAnimate Imperative sequences (animate(scope.current, …))
useMotionValue Animated state that does not re-render on every frame
useTransform Deriving one motion value from another
useSpring Spring-smoothing a motion value
useMotionTemplate Composing motion values into a string (e.g. a CSS filter)
useMotionValueEvent Subscribing to a motion value's changes
useInView Whether an element is in the viewport
usePresence / useIsPresent Custom exit choreography inside AnimatePresence
useAnimationFrame A per-frame callback

Types: MotionProps, MotionStyle, HTMLMotionProps, SVGMotionProps, Variants, Transition, TargetAndTransition, VariantLabels, MotionValue, AnimationPlaybackControls, AnimatePresenceProps, MotionConfigProps, UseInViewOptions.

Gesture props (whileHover, whileTap, whileDrag, drag) are props on motion.* elements, not separate imports.

Anything not in the tables above is not part of the platform surface, even if motion.dev documents it — Reorder, LazyMotion, m, useScroll and Motion's internals are deliberately excluded. Use boardweaver/dnd for dragging things around; it is the blessed drag surface and handles keyboard dragging, which Motion's drag does not.

Notes on the upstream documentation

  • Where motion.dev writes import { motion } from "motion/react", use import { motion } from "boardweaver/motion". Importing motion, motion/react or framer-motion directly is a compile error — those specifiers do not exist inside a game.
  • The site documents Motion's current release, which may be newer than the 12.43.0 bundled here. If a prop it describes is a type error in your game, that is the likely reason.