Skip to content

Scale Entrances

Menus, popovers and dialogs often enter with a bit of scale, so they seem to arrive instead of switching on. But a scale entrance that starts at zero looks like a cartoon zoom. One that starts at 0.95 looks like a small settle.

Open both menus. Same duration, same easing, different starting scale.

Wrong
From scale(0)
Right
From scale(0.95)

The left menu spends the start of its animation as an unreadable smudge, because the text scales with the box. The right one is readable from the first frame. The scale is a hint of movement, not the movement itself.

Start a scale entrance between 0.9 and 0.97, fade it in, and grow it from the thing that opened it.

Grow from the trigger

By default an element scales from its center. That suits a dialog in the middle of the screen. A menu under a button in the corner should grow out of that corner, so it looks attached to what you clicked. The dot marks the origin.

Wrong
Origin: center
Right
Origin: top right

Same 0.9 start, same timing. The only change is transform-origin.

A positioning library already knows where the trigger is. Base UI exposes it on every anchored popup as --transform-origin, so one line covers every side the menu can open on. Radix has a variable for each component, like --radix-dropdown-menu-content-transform-origin.

Find your number

Drag the slider to change where the menu starts. It is slowed to 480ms so you can see the shape of it.

Below 0.8 it starts to read as a zoom. Above 0.98 you may as well drop the scale and keep the fade.

Usage

app.tsx
<Popover.Popup
  className="origin-(--transform-origin) transition-[opacity,scale] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]
    data-starting-style:scale-95 data-starting-style:opacity-0
    data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:duration-100"
>
  ...
</Popover.Popup>
.popup {
  transform-origin: var(--transform-origin);
  transition:
    opacity 200ms cubic-bezier(0.23, 1, 0.32, 1),
    scale 200ms cubic-bezier(0.23, 1, 0.32, 1);
}

.popup[data-starting-style],
.popup[data-ending-style] {
  opacity: 0;
  scale: 0.95;
}

.popup[data-ending-style] {
  transition-duration: 100ms;
}

--transform-origin only exists inside Base UI's anchored popups. For a menu you place yourself, set the origin to the corner nearest the trigger, like origin-top-right for a menu that opens below a right-aligned button.

Resources

Search concepts

Search for a command to run...