# How to Implement transform-origin for Popovers and Dropdowns

> Learn to implement transform-origin for popovers and dropdowns by setting a CSS custom property via JavaScript. Achieve animations originating from the trigger's position.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-07-29

---

**Set a CSS custom property `--transform-origin` to the trigger element's center coordinates via JavaScript before opening the popover, allowing the scale animation to originate from the trigger's position rather than the element's geometric center.**

Popovers and dropdowns are UI elements anchored to a trigger such as a button or icon. According to the animation standards in the `emilkowalski/skills` repository, these components should animate from the trigger's position to maintain spatial continuity. This technique, codified in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), uses a dynamic CSS custom property to bridge JavaScript calculations with CSS transitions.

## The CSS Foundation Using Custom Properties

The baseline CSS establishes a shared animation contract through the `--transform-origin` variable. In [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 56-58), the standard mandates that popovers read from this variable rather than using a static value.

Define the transform origin to use the variable, with a safe fallback to `center`:

```css
/* Base UI standard from STANDARDS.md */
.popover,
.dropdown {
  transform-origin: var(--transform-origin, center);
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
}

/* Open state triggers the animation */
.popover[data-open],
.dropdown[data-open] {
  opacity: 1;
  transform: scale(1);
}

```

The `var(--ease-out)` reference corresponds to the easing curves defined in lines 31-34 of the same standards file. By keeping the origin declarative via CSS variables, any component following the `.popover` or `.dropdown` class convention automatically inherits origin-aware behavior without duplicating JavaScript logic.

## Calculating the Trigger Position

Before the popover opens, JavaScript must compute the trigger's center point and inject it into the element's style. The [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) file outlines this pattern: read the trigger's bounding box, calculate the center, and write the value to `--transform-origin`.

```javascript
/**
 * Opens a popover with origin-aware animation.
 * Calculates trigger center and sets --transform-origin before the transition runs.
 * 
 * @param {HTMLElement} trigger - The element that activated the popover
 * @param {HTMLElement} popover - The popover element to animate
 */
function openPopover(trigger, popover) {
  // Get trigger dimensions relative to viewport
  const { left, top, width, height } = trigger.getBoundingClientRect();
  
  // Calculate center point
  const originX = left + width / 2;
  const originY = top + height / 2;
  
  // Inject into CSS custom property
  popover.style.setProperty('--transform-origin', `${originX}px ${originY}px`);
  
  // Trigger the CSS transition by setting the open state
  popover.setAttribute('data-open', '');
  
  // Accessibility: manage focus and aria-expanded here
}

```

This calculation uses `getBoundingClientRect` to obtain viewport-relative coordinates, ensuring the origin point remains accurate even if the user has scrolled the page.

## Dropdown Implementation and Modal Exceptions

Dropdowns follow the identical pattern. The [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) notes that while popovers and dropdowns use dynamic origins, **modals remain centered** and should explicitly use `transform-origin: center` since they lack a specific trigger anchor.

```javascript
function toggleDropdown(trigger, dropdown) {
  const { left, top, width, height } = trigger.getBoundingClientRect();
  const originX = left + width / 2;
  const originY = top + height / 2;
  
  dropdown.style.setProperty('--transform-origin', `${originX}px ${originY}px`);
  
  const isOpen = dropdown.hasAttribute('data-open');
  dropdown.toggleAttribute('data-open', !isOpen);
}

```

The architectural flow remains consistent across both components: compute coordinates, set the property, then toggle visibility. This separation of concerns keeps the animation logic in CSS while the positioning logic lives in JavaScript.

## Summary

- **Use CSS variables**: Define `transform-origin: var(--transform-origin, center)` in your base popover and dropdown styles as specified in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md).
- **Calculate trigger center**: Use `getBoundingClientRect()` to find the trigger's viewport position, then compute `left + width/2` and `top + height/2`.
- **Set before animating**: Call `element.style.setProperty('--transform-origin', ...)` before adding the open state or class that triggers CSS transitions.
- **Center modals**: Keep `transform-origin: center` for modals since they have no anchor element, as noted in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md).
- **Maintain standards**: Reference the easing functions and timing constants defined in the repository's animation standards for consistent motion.

## Frequently Asked Questions

### Why can't I just use `transform-origin: center` for popovers?

Using `center` causes the popover to expand from its own geometric middle, which breaks the spatial relationship with the trigger button. According to the principles in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), anchoring the animation origin to the trigger's position creates a more responsive feel and clarifies the connection between the control and its resulting panel.

### How do I handle transform-origin when the popover appears above the trigger?

The calculation remains the same regardless of final placement. Calculate the trigger's center coordinates using `getBoundingClientRect()`, and if your positioning logic places the popover above the trigger, the CSS `transform-origin` will still point to the correct viewport location. The origin calculation is independent of the popover's final `top` or `left` position.

### Should dropdown menus use the same transform-origin technique?

Yes. The [`skills/find-animation-opportunities/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/find-animation-opportunities/SKILL.md) explicitly includes dropdown menus, panels, and popovers as candidates for origin-aware animations. Apply the same JavaScript calculation and CSS variable pattern to dropdowns to maintain consistency across your component library.

### What happens if I forget to set the `--transform-origin` variable?

The CSS fallback `var(--transform-origin, center)` ensures graceful degradation. If the JavaScript fails to set the property, the animation simply originates from the element's center rather than the trigger's position, maintaining functionality while losing the enhanced spatial effect.