{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dynamic-island",
  "title": "Dynamic Island",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A black pill or MacBook-style notch that morphs between live activities (timers, uploads, an AI thinking state, notifications) with a spring layout animation. Each view is announced politely.",
  "dependencies": [
    "motion@^12"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/dynamic-island.tsx",
      "content": "// Ballmac UI: Dynamic Island. https://ui.ballmac.com/components/dynamic-island\n\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, MotionConfig, motion, type Transition } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/** The island's spring: fast, with the small overshoot that makes it feel alive. */\nconst islandSpring: Transition = { type: \"spring\", stiffness: 420, damping: 30, mass: 0.9 }\n\ntype DynamicIslandViewProps = React.ComponentProps<\"div\"> & {\n  /** Identifies this view; the island shows the view whose value matches its `view` prop. */\n  value: string\n  /** Corner radius in px while this view is showing. Around half the height gives a pill. */\n  radius?: number\n  /** Announced to screen readers when this view appears (the visual content is not re-read on every change). */\n  label?: string\n}\n\n/**\n * One state of the island. Size it with className (e.g. `h-9 w-32`, or padding around content):\n * the island morphs to whatever size the active view has.\n */\nfunction DynamicIslandView({ value, radius: _radius, label: _label, className, ...props }: DynamicIslandViewProps) {\n  return <div data-slot=\"dynamic-island-view\" data-value={value} className={cn(\"flex items-center\", className)} {...props} />\n}\n\ntype DynamicIslandProps = React.ComponentProps<\"div\"> & {\n  /** Value of the view to show. Changing it morphs the island to the new view. */\n  view: string\n  /** \"pill\" floats with all corners rounded; \"notch\" hangs from the top edge with square top corners, like a MacBook notch. */\n  shape?: \"pill\" | \"notch\"\n}\n\n/**\n * A black surface that morphs between views with a spring layout animation, like the iPhone Dynamic Island\n * or a MacBook notch app. Pass `<DynamicIslandView value=\"…\">` children and choose one with `view`.\n */\nfunction DynamicIsland({ view, shape = \"pill\", className, children, ...props }: DynamicIslandProps) {\n  const views = React.Children.toArray(children).filter(\n    (child): child is React.ReactElement<DynamicIslandViewProps> => React.isValidElement(child) && typeof (child.props as DynamicIslandViewProps).value === \"string\"\n  )\n  const active = views.find((child) => child.props.value === view) ?? views[0]\n  const radius = active?.props.radius ?? 22\n  const notch = shape === \"notch\"\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div data-slot=\"dynamic-island-root\" data-shape={shape} className={cn(\"flex justify-center\", className)} {...props}>\n        <motion.div\n          layout\n          data-slot=\"dynamic-island\"\n          data-view={active?.props.value}\n          initial={false}\n          animate={{\n            borderTopLeftRadius: notch ? 0 : radius,\n            borderTopRightRadius: notch ? 0 : radius,\n            borderBottomLeftRadius: radius,\n            borderBottomRightRadius: radius,\n          }}\n          transition={islandSpring}\n          className={cn(\n            \"relative isolate overflow-hidden bg-black text-white antialiased\",\n            notch\n              ? \"shadow-[0_10px_30px_-10px_rgb(0_0_0/0.55)]\"\n              : \"shadow-[0_0_0_1px_rgb(255_255_255/0.06),0_10px_30px_-10px_rgb(0_0_0/0.55)]\"\n          )}\n        >\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {active ? (\n              <motion.div\n                key={active.props.value}\n                layout=\"position\"\n                initial={{ opacity: 0, scale: 0.92, filter: \"blur(6px)\" }}\n                animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\", transition: { ...islandSpring, delay: 0.06 } }}\n                exit={{ opacity: 0, scale: 0.92, filter: \"blur(6px)\", transition: { duration: 0.14 } }}\n              >\n                {active}\n              </motion.div>\n            ) : null}\n          </AnimatePresence>\n        </motion.div>\n        <span role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-slot=\"dynamic-island-announcer\" className=\"sr-only\">\n          {active?.props.label ?? \"\"}\n        </span>\n      </div>\n    </MotionConfig>\n  )\n}\n\nexport { DynamicIsland, DynamicIslandView, islandSpring, type DynamicIslandProps, type DynamicIslandViewProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/dynamic-island.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "dynamic island",
      "notch",
      "macos",
      "ios",
      "live activity",
      "status",
      "motion",
      "morph"
    ],
    "version": "1.0.0",
    "updated": "2026-09-29",
    "ai": {
      "summary": "<DynamicIsland view={state}> renders whichever <DynamicIslandView value=\"…\"> child matches and springs its size and corner radius to fit. Size each view with className (h-/w-/padding); set radius per view; label is announced via a polite live region.",
      "whenToUse": [
        "Showing one live background activity at a time: an upload, a build, a timer, an AI response in progress",
        "A compact status surface pinned to the top of an app or a product mockup",
        "Landing-page heroes that demo a Mac notch or iPhone app"
      ],
      "whenNotToUse": [
        "Several notifications that need to stay visible together (use notification-stack)",
        "Long or critical messages that must not disappear (use a dialog or an inline alert)"
      ],
      "composesWith": [
        "dock"
      ],
      "a11y": [
        {
          "keys": "—",
          "action": "The active view's label is announced in a polite live region; interactive content inside a view is reachable with Tab"
        }
      ],
      "customization": [
        "view: the active view value (controlled)",
        "shape: pill | notch (square top corners, hangs from the top edge)",
        "DynamicIslandView: value, radius (px, default 22), label (announced), className for size and layout",
        "Always black like the hardware it imitates; accent content with chart-1..5 tokens",
        "Under reduced motion the island switches views without the morph"
      ]
    },
    "examples": [
      "dynamic-island-demo",
      "dynamic-island-pill"
    ],
    "url": "https://ui.ballmac.com/components/dynamic-island"
  },
  "categories": [
    "macos"
  ],
  "type": "registry:ui"
}