# Why `transform-origin: center` Is Wrong for Popovers (And What to Use Instead)

> Discover why transform-origin center breaks popover animations and learn how to use trigger-aware origins with --transform-origin for a connected experience.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: best-practices
- Published: 2026-08-06

---

**`transform-origin: center` breaks the spatial relationship between a popover and its trigger, making the animation appear disconnected; popovers should instead use a trigger-aware origin via the custom property `--transform-origin`.**

Popovers—tooltips, menus, dropdowns—are **trigger-anchored UI elements**. They exist because a user clicked or hovered a specific control, and their animation should visually communicate that relationship. In the `emilkowalski/skills` repository, the design system explicitly rejects `transform-origin: center` for these components. This article explains why, based on the Base UI guidelines documented throughout the repo.

---

## The Problem with `transform-origin: center`

When you set `transform-origin: center` on a popover, the scaling animation originates from the element's own geometric midpoint. The result is a jarring effect: the popover appears to materialize from nowhere rather than emerging from the button that spawned it.

This violates a fundamental principle of UI motion: **visual continuity**. Users perceive animated elements as continuations of the interactions that caused them. A disconnected origin breaks that mental model.

---

## The Base UI Solution: `--transform-origin`

The repository's design system solves this with a runtime-computed custom property. As documented in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (line 236), the correct approach is:

```css
.popover {
  transform-origin: var(--transform-origin);
}

```

The `--transform-origin` property is calculated from the trigger element's position and applied dynamically. This ensures the popover scales from the point where the user initiated the interaction.

### How the Calculation Works

The JavaScript derives the origin coordinates relative to the popover's position:

```javascript
const trigger = document.getElementById('trigger');
const popover = document.getElementById('myPopover');

trigger.addEventListener('click', () => {
  const rect = trigger.getBoundingClientRect();
  const originX = rect.left + rect.width / 2 - popover.offsetLeft;
  const originY = rect.top + rect.height / 2 - popover.offsetTop;
  popover.style.setProperty('--transform-origin', `${originX}px ${originY}px`);
  popover.toggleAttribute('data-open');
});

```

The trigger's center point is computed, offset by the popover's position, then injected into the CSS custom property. The animation now scales from that precise point.

---

## Complete Implementation Example

Here's a production-ready popover with proper origin-aware scaling:

```html
<button id="trigger">Open Menu</button>

<div class="popover" id="myPopover">
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>

```

```css
.popover {
  /* Base UI pattern: fallback to center if custom property unavailable */
  transform-origin: var(--transform-origin, center);
  transform: scale(0);
  opacity: 0;
  transition: 
    transform 0.15s ease-out,
    opacity 0.1s ease-out;
  position: absolute;
}

.popover[data-open] {
  transform: scale(1);
  opacity: 1;
}

```

The `var(--transform-origin, center)` syntax provides graceful degradation. If the custom property isn't set—whether due to script failure or an unsupported environment—the popover still functions with a centered origin.

---

## The Exception: Modals

**Modals are not trigger-anchored.** They appear centered in the viewport, disconnected from any specific control. For these components, `transform-origin: center` remains appropriate.

As noted in [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md) (lines 79-82), this exemption is intentional:

```css
.modal {
  /* Modals centered in viewport: default origin is correct */
  transform-origin: center;
  /* ...other modal styles... */
}

```

The distinction hinges on **spatial anchoring**. Popovers maintain a relationship to their trigger; modals assert independence from the underlying interface.

---

## Architectural Rationale

The `transform-origin: center` prohibition in `emilkowalski/skills` rests on three pillars documented across multiple skill files:

1. **Spatial consistency** — Motion perceived as continuous with user action reinforces causality and predictability.

2. **Accessibility and predictability** — Consistent origin placement reduces cognitive load for assistive technology users and minimizes motion-sickness triggers.

3. **Design system uniformity** — The `--transform-origin` variable provides a single source of truth, enforced through standards in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 54-56).

---

## Source File Reference

| File | Relevant Content |
|------|----------------|
| [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (line 236) | "Make popovers origin-aware" — explains the problem and introduces `var(--transform-origin)` |
| [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 54-56) | "Origin-aware popovers" — codifies the CSS rule in animation standards |
| [`skills/animate/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md) (lines 79-82) | Reinforces rule and modal exemption |
| [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) (lines 52-53) | Diagnostic discussion of `center` origin problems |
| [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) (lines 53-56) | Concrete CSS snippet for audits |

---

## Summary

- **`transform-origin: center`** disconnects popover animations from their triggers, creating disjointed UX.
- **Trigger-aware origins** via `var(--transform-origin)` preserve spatial relationships and user mental models.
- **JavaScript calculates** the origin from trigger coordinates at runtime.
- **Modals are exempt** because they lack trigger anchoring.
- The **Base UI pattern** with fallback syntax ensures robust, progressive enhancement.

---

## Frequently Asked Questions

### Should I never use `transform-origin: center`?

Not never—just not for trigger-anchored components. Popovers, tooltips, dropdowns, and menus all benefit from trigger-aware origins. Modals, toast notifications, and other viewport-centered elements can safely use `center`.

### How does `--transform-origin` get its value?

JavaScript computes it from the trigger element's `getBoundingClientRect()`, adjusts for the popover's position, and sets the custom property via `element.style.setProperty()`. UI libraries like those referenced in this repo automate this calculation.

### What happens if JavaScript fails to set the property?

The fallback value `var(--transform-origin, center)` ensures the animation still works. The origin won't be trigger-aware, but the component remains functional—a core tenet of progressive enhancement.

### Is this pattern specific to this repository's design system?

The `--transform-origin` naming convention and specific implementation derive from the Base UI system in `emilkowalski/skills`. However, the underlying principle—matching transform origin to user interaction point—applies universally across modern UI development.