← Case study

Detail 07 · Interaction

Dynamic islands

Islands instead of a menu: a dock of pills that morph open through a blur, with sketched icons, the music island and the resume island. 7 minutes, 2 demos.

Try it: open the island

01 · The idea

No menu, just islands

This site has very few places to go: home, my résumé, ways to reach me, and some music if you want it. A menu would hide those few things behind a click, and a row of text links would make the header look like every other portfolio.

So each one is an island: a small pill that opens in place into what it holds, the way the iPhone’s Dynamic Island grows out of the top of the screen. The logo is the way home. The pills already show what’s inside them, as tiny pencil sketches, so you know what you’ll get before you open one.

Only one island is open at a time; the other tucks away so the open one stays centred. On phones the dock moves to the bottom of the screen, within reach of a thumb, and the islands open upwards.

02 · Play with it

The island, taken apart

Below is the social island, built from the same kit as the real one and with its parts on switches. Open it at the live speed first, then slow it down and switch things off one at a time.

The island, taken apart
Speed

Four things make the morph feel like one object changing shape rather than a box being resized:

  • A spring with a little give. It settles with a soft overshoot instead of stopping dead, so the pill feels like it has weight.
  • A blur through the middle. The island blurs as it starts to change and sharpens as it settles, which hides the in-between frames where nothing is quite one shape or the other.
  • Content that waits. The pill’s icons fade out at once; the open content fades in a beat later, once there’s room for it.
  • Content anchored at full size. The open content is laid out at its final size and pinned to the island’s top edge from the first frame. The growing island uncovers it, so nothing inside is ever squeezed or reflowed.

03 · How it’s built

One kit, three islands

Every island is built from the same few pieces, so they line up in the dock and move alike: one spring, one pill size, one shadow, one outline.

The spring, and a pill sized by its icons
// Smooth with a gentle settle, like the iPhone's Dynamic Island: the site's morph
export const ISLAND_SPRING = SPRING.morph;
export const HOVER_SPRING = SPRING.playful;

/*
  The closed pill, the same for every island so they line up in the dock:
  one height, one icon size and gap, and side padding of half the height, so
  the first and last icons sit where the rounded ends begin.
*/
export const PILL = { height: 36, icon: 14, gap: 12 };

/** An island's closed size, from how many icons it shows. */
export const pillSize = (icons) => ({
  width: PILL.height + icons * PILL.icon + (icons - 1) * PILL.gap,
  height: PILL.height,
  borderRadius: PILL.height / 2,
});

The blur is a keyframe animation on the island itself, played on every change of open state except the first render.

Blurring through the morph
// The island blurs mid-morph and sharpens as it settles, like the Dynamic Island
export const MORPH_BLUR = { filter: ["blur(0px)", "blur(8px)", "blur(0px)"] };
export const MORPH_BLUR_TIMING = { duration: SPRING.morph.visualDuration, times: [0, 0.3, 1], ease: "easeOut" };

/** Blurs `ref`'s element through each open/close morph (not on first render). */
export function useMorphBlur(ref, isOpen) {
  const hasMountedRef = useRef(false);
  useEffect(() => {
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      return;
    }
    const controls = animate(ref.current, MORPH_BLUR, MORPH_BLUR_TIMING);
    return () => controls.stop();
  }, [ref, isOpen]);
}

The dock holds which island is open. The others shrink to nothing and fade, on the same spring, so the open one slides to the centre rather than jumping there.

One island open at a time
const slot = (name, island, gapBefore = 0) => {
  const hidden = isHidden(name);
  return (
    <motion.div
      initial={false}
      animate={{
        width: hidden ? 0 : "auto",
        marginLeft: allShown ? gapBefore : 0,
        opacity: hidden ? 0 : 1,
        scale: hidden ? 0.9 : 1,
      }}
      transition={ISLAND_SPRING}
      inert={hidden}

A hidden island is also inert, so a keyboard can’t tab into something you can’t see.

04 · The icons

Sketches that colour in

The icons are the real brand logos, drawn as pencil sketches. Each logo’s outline is traced twice through two different noise filters, so the two passes drift apart the way a quick sketch goes over its lines. Point at one and it colours in from the bottom, like filling it in with a marker, while the pencil fades.

Sketches that colour in

Let's connect

The colouring is a mask on the real logo: a gradient band with a soft top edge, rising as a motion value goes from 0 to 1. It never re-renders the component; only the mask moves.

A soft-edged band of colour, rising
// A soft-edged band of color rises from below the icon to above it
const inkTop = useTransform(fill, (p) => p * (100 + INK_EDGE));
const inkSolid = useTransform(inkTop, (top) => top - INK_EDGE);
const inkMask = useMotionTemplate`linear-gradient(to top, #000 ${inkSolid}%, transparent ${inkTop}%)`;
const sketchOpacity = useTransform(fill, [0, 0.6, 1], [1, 0.6, 0]);

05 · The other two

The music island and the résumé island

The music island sits in the navbar, not the dock, but it’s built the same way. It’s pinned at its top-right corner and grows left and down around its waveform, which never moves, so it opens without the wave jumping. The wave swells with each beat of the music. It opens on hover, and once by itself a few seconds after the intro to offer a song. Open, it’s a small music player: previous, play/pause and next, the play ⇄ pause the same hand-drawn shape as the demos’ play buttons, and skipping fades the old track out rather than cutting it. The pencil rule above the controls is the progress bar: an ember wave grows along it as the song plays, flowing with the island’s wave and flattening with it on pause. On a touch screen, where nothing hovers, the first tap opens it and the next one plays.

The résumé island opens to a thumbnail of the real résumé: the same coded component as the résumé page, not a picture of it. Click it and the thumbnail grows into the full page, which has a detail of its own later in this case study.

06 · Decisions

Why it’s built this way

Show what's inside

A closed pill shows sketches of what it holds. You never open something to find out what it is.

Open in place

Links, a résumé preview, a song: each is a glance, so none of them costs a page change.

One open at a time

Opening one island tucks the other away. The dock never turns into a pile of panels.

One kit for all

Same pill height, same spring, same outline. They line up and move alike because they’re built from the same parts.

Crafts has its own island too, between the social and résumé ones: it opens on the case study’s book, lying closed, and a click carries the book itself to the case study’s page.

07 · Takeaways

What to keep

  • If a site has few destinations, show them; let each open in place instead of hiding them in a menu.
  • A morph reads as one object when it springs, blurs through the middle, and uncovers content laid out at its final size.
  • Build every island from one shared kit, so they line up and move alike.
The real dock→

The islands are at the top of every page, this one included. On the home page, the music island opens by itself after the intro.