Web Dynamic Island

April 21st, 2024

Interactive

A lot of you may know the Dynamic Island, that controversial little pill at the top of every iPhone screen since the iPhone 14 Pro. At its release, this piece of software was discussed quite a lot, and many people saw it as unnecessary. I was also quite skeptical as well. But since I use an iPhone with the Dynamic Island, I am just blown away by it.

Why it feels so good

Using the Dynamic Island feels so good because the Apple developers did a great job animating it. The little microinteractions are so smooth and fluid that they just feel right. Every time I discover a new Dynamic Island animation, it makes me, as an interaction designer, smile.

Dynamic Island on the Web

So, I decided to clone the Dynamic Island for the web. I used React and Framer Motion to create the animations. The result is the component you see above.

Create the Base Component

First, I created the base component. It receives the active prop, which is a string that determines which state is currently active. My version has 3 states: default, event, and call.


interface DynamicIslandProps {
  children?: React.ReactNode;
  className?: string;
  active: DynamicIslandTypes;
}

const DynamicIsland = ({
children,
active,
className = "min-w-[125px] min-h-10",
}: DynamicIslandProps) => {
return (

<motion.div
  className={cn("relative  bg-black rounded-full", className)}
  layout
  style={{ transformOrigin: "top", originX: 0.5, originY: 0 }}
  transition={{
    layout: {
      duration: 0.3,
      stiffness: 110,
      damping: 12,
      type: "spring",
    },
  }}
>
  {children}
</motion.div>
)};

export default DynamicIsland;

Animation

To achieve this animation style, I used the layout animation from Framer Motion. Layout animations help you animate the position and sizes of elements on rerender. You can simply enable layout animations by passing the layout prop to a Motion component.

<motion.div
  layout
  style={{ transformOrigin: "top", originX: 0.5, originY: 0 }}
  transition={{
    layout: {
      duration: 0.3,
      stiffness: 110,
      damping: 12,
      type: "spring",
    },
  }}
>
  {children}
</motion.div>
)};

I played around with different animation styles and the parameters damping, stiffness, and duration to get the right feel for the animation. To ensure the island is changing size from the right origin, I changed the origin to originX: 0.5, originY: 0.

Render the correct content

Then I rendered the DynamicIsland component inside a Wrapper Component. I managed the state here and conditionally rendered the correct inner content based on the active state.

In 5 min

Daily Coffee ☕

10:00 → 11:00

ASASAS
CN

mobile

Amelie


const DynamicIslandWrapper = () => {
  const [active, setActive] = useState<DynamicIslandTypes>("default");
  return (
    <div >
      <DynamicIsland active={active}>
        {active === "call" && <DynamicCall />}
        {active === "event" && <DynamicEvent />}
      </DynamicIsland>
    
      {... here the Buttons to change the state}
    </div>
  );
};

export default DynamicIslandWrapper;

Because many of you asked for the source code, I decided to share it with you. You can find the full source code on my GitHub.

Next Interaction

NLP Input