Best Practices for Modal vs Popover Transform Origins
Set popover transform-origin to the trigger element's center using the --transform-origin CSS variable, while keeping modal transform-origin at center to ensure spatially coherent animations.
When implementing animated overlays in the emilkowalski/skills repository, the choice of transform-origin directly impacts perceived spatial relationships and motion realism. According to the Animation Standards Reference documented in STANDARDS.md, these two patterns require fundamentally different approaches to maintain visual consistency and user expectations.
The Core Distinction
The emilkowalski/skills codebase establishes a strict dichotomy between anchored elements and viewport-centered overlays. This distinction is enforced during code reviews and documented in the "Physicality" section of STANDARDS.md and the "transform-origin" reference table in SKILL.md.
Popovers, Dropdowns, and Tooltips
Anchored elements must scale from their trigger's center. Popovers are spatially tied to the button or element that opened them, so their animation should communicate this relationship.
- Use
transform-origin: var(--transform-origin)where the custom property calculates to the trigger's geometric center - This creates a "source-to-target" motion that reinforces the mental model of the UI emerging from the user's click point
- Transition duration should be approximately 150ms for these smaller, contextual elements
Modals and Drawers
Full-screen overlays must scale from the viewport center. Unlike popovers, modals are not anchored to specific triggers; they appear as independent layers above the entire interface.
- Keep
transform-origin: centerto preserve symmetric, balanced entry animations - This avoids implying false directional cues when no specific source element exists
- Use longer transitions (around 250ms) to match the larger surface area and dramatic context shift
Implementation Examples
The following patterns from the emilkowalski/skills repository demonstrate the correct application of these rules.
CSS Implementation
/* Popover – scales from the trigger position */
.popover {
transform-origin: var(--transform-origin);
transition: transform 150ms ease-out, opacity 150ms ease-out;
transform: scale(0.95);
opacity: 0;
}
.popover[data-open] {
transform: scale(1);
opacity: 1;
}
/* Modal – scales from center */
.modal {
transform-origin: center;
transition: transform 250ms ease-out, opacity 250ms ease-out;
transform: scale(0.95);
opacity: 0;
}
.modal[data-open] {
transform: scale(1);
opacity: 1;
}
Dynamic Origin Calculation (React/TypeScript)
When using Base UI or similar libraries, calculate the --transform-origin property dynamically based on the anchor element's bounding box:
import { useRef, useEffect } from "react";
function Popover({ open, anchorRef, children }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (anchorRef.current && ref.current) {
const rect = anchorRef.current.getBoundingClientRect();
const originX = rect.left + rect.width / 2;
const originY = rect.top + rect.height / 2;
ref.current.style.setProperty(
"--transform-origin",
`${originX}px ${originY}px`
);
}
}, [anchorRef, open]);
return (
<div
ref={ref}
className="popover"
data-open={open}
role="dialog"
aria-modal="false"
>
{children}
</div>
);
}
Critical Implementation Notes
Avoid common pitfalls that break the spatial consistency established in the emil-design-eng standards:
- Never use
scale(0)on entry; start fromscale(0.9)toscale(0.97)combined with opacity fades to create natural-looking reveals - Do not override the origin for modals; the "modals are exempt" rule is the only explicit exception to the trigger-origin pattern
- Ensure the
--transform-originvariable is updated when the trigger moves or the viewport resizes to prevent misaligned animations
Why the Distinction Matters
Understanding these rules requires recognizing how users perceive spatial relationships in interfaces.
Spatial Consistency – Users expect a popover to emerge from the button they pressed. If it scales from its own geometric center, the motion feels disjointed, breaking the cognitive link between trigger and result.
Visual Balance – Modals cover large areas and often dim the background. Center-origin scaling creates a symmetric, calm entrance appropriate for full-screen interruptions, avoiding the implication that the content originated from a specific screen location.
Summary
- Popovers must use
transform-origin: var(--transform-origin)pointing to the trigger's center to maintain spatial relationships - Modals must use
transform-origin: centerto preserve visual balance and avoid false directional cues - Always start scale animations from
0.95or higher, never0, paired with opacity transitions - Reference
STANDARDS.mdandSKILL.md(review-animations section) for the canonical implementation checklist
Frequently Asked Questions
Can I use center transform-origin for popovers if they appear near the trigger anyway?
No. According to the STANDARDS.md Physicality section, popovers must scale from the trigger's actual geometric center, not merely near it. Using center creates a subtle but perceptible disconnect between the user's click point and the animation origin, undermining the spatial mental model even when the popover is visually proximate.
Why should I avoid starting animations from scale(0)?
Starting from scale(0) creates an unnatural "popping" effect that violates the physicality principles documented in the emilkowalski/skills repository. The human eye expects objects to have mass and volume; scaling from 0.9 or 0.95 with an opacity fade simulates a natural reveal, while 0 suggests the materialization of new matter.
How do I calculate the trigger's center for the --transform-origin variable?
Calculate the trigger's center by adding half its width to its left offset and half its height to its top offset from the viewport. In JavaScript: const originX = rect.left + rect.width / 2 and const originY = rect.top + rect.height / 2. Set these as the --transform-origin CSS custom property in the format ${originX}px ${originY}px on the popover element.
Where are these transform origin rules documented?
These guidelines are codified in STANDARDS.md under the Animation Standards Reference and Physicality sections, with practical checklists in SKILL.md under the review-animations and emil-design-eng sections. These files define the canonical approach for handling transform origins across the emilkowalski/skills codebase.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →