Home / Skills / Web & UI Design / Design Engineering (Emil Kowalski)
Web & UI Design

Design Engineering (Emil Kowalski)

Emil KowalskiMITSkill language: English
License verifiedNo scriptsLow risk
DemoReal run · claude-sonnet-5-5 · October 2026
Please review this UI code for polish and animation problems. ```css .btn { transition: all 300ms; } .menu { transform: scale(0); transform-origin: center; transition: transform 500ms ease-in; } .menu.open { transform: scale(1); } .card:hover { transform: scale(1.05); } .row { opacity: 0; animation: slideIn 600ms ease-in-out forwards; } ``` ```jsx <motion.div animate={{ x: open ? 0 : -300 }} /> // Cmd+K opens the command palette with a 400ms fade and slide ``` The .menu is a dropdown anchored to a profile button; the row animation plays on a list of 40 items all at once.
Skill: emil-design-eng

Here's the review.

Before After Why
.btn { transition: all 300ms; } transition: transform 160ms ease-out; plus .btn:active { transform: scale(0.97); } all animates properties you didn't intend to animate and can trigger layout and paint. 300ms is too slow for button feedback (aim for 100-160ms). The button also has no press state.
.menu { transform: scale(0) } transform: scale(0.95); opacity: 0; and .menu.open { transform: scale(1); opacity: 1; } Nothing in the real world appears from nothing. scale(0) makes the menu pop out of a point.
transform-origin: center on the profile dropdown transform-origin: top right (or the trigger's side), or var(--transform-origin) if you use Base UI or Radix A popover should grow from its trigger. Only modals stay centered.
transition: transform 500ms ease-in transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1), opacity 180ms ease-out; ease-in starts slow, which is exactly when the user is watching. 500ms is far too long for a dropdown (150-250ms). Make the close faster than the open, around 120ms.
.card:hover { transform: scale(1.05); } Wrap it in @media (hover: hover) and (pointer: fine), drop to about scale(1.02), and add transition: transform 200ms ease-out on .card Touch devices fire hover on tap, so the card sticks scaled. There is also no transition, so the hover snaps. 1.05 is heavy for a card, and it resizes the content and can cause neighbors to look cramped.
.row { animation: slideIn 600ms ease-in-out forwards; } on 40 items at once animation: slideIn 250ms cubic-bezier(0.23, 1, 0.32, 1) both; with animation-delay: calc(var(--i) * 30ms), and cap the stagger (for example min(var(--i), 8)) Entering elements need ease-out, not ease-in-out. 600ms is too long. All 40 rows moving in unison reads as one block, so stagger them by 30-80ms. Don't let the stagger run for seconds (40 × 30ms is 1.2s), so cap it. Never block interaction while it plays.
.row { opacity: 0; ... } as the base style Put the hidden state in the keyframe's from and use animation-fill-mode: both If the animation fails or is disabled, rows stay invisible forever. Also consider @starting-style or transitions if rows get added dynamically, since keyframes restart from zero when interrupted.
<motion.div animate={{ x: open ? 0 : -300 }} /> animate={{ transform: open ? "translateX(0)" : "translateX(-100%)" }} with an explicit transition, such as a spring (duration: 0.3, bounce: 0) The x shorthand runs on the main thread and drops frames under load. A hardcoded -300 breaks if the panel width changes, so percentages are safer. Springs also keep velocity if the user toggles mid-motion.
Cmd+K palette with a 400ms fade and slide No animation. Render it instantly. A keyboard shortcut used hundreds of times a day should feel instant. Raycast does this. If you want some softness, use at most a 100ms opacity-only fade, and none on close.
No reduced-motion handling anywhere @media (prefers-reduced-motion: reduce) keeps opacity fades and removes the slide, scale and translate motion. In Motion, use useReducedMotion(). Reduced motion means gentler, not zero. Keep the fades that aid comprehension and remove the movement.

Priorities

  1. Remove the Cmd+K animation. It has the biggest impact on how the app feels.
  2. Fix the dropdown (scale(0), ease-in, 500ms, center origin), since it hits every issue at once.
  3. Stagger and shorten the list animation.
  4. Clean up transition: all, the hover gating, and the Motion x prop.

Suggested CSS for the dropdown and rows

:root { --ease-out: cubic-bezier(0.23, 1, 0.32, 1); }

.menu {
  opacity: 0;
  transform: scale(0.95);
  transform-origin: top right; /* match the profile button's position */
  transition: transform 120ms var(--ease-out), opacity 120ms ease-out;
}
.menu.open {
  opacity: 1;
  transform: scale(1);
  transition-duration: 180ms;
}

.row {
  animation: slideIn 250ms var(--ease-out) both;
  animation-delay: calc(min(var(--i), 8) * 40ms);
}
@keyframes slideIn {
  from { opacity: 0; transform: translateY(8px); }
}

The menu's closed state needs pointer-events: none or visibility: hidden so it isn't clickable while invisible.

Test run: a pasted CSS and JSX snippet with common animation mistakes. The model used the required Before, After, Why table, flagged the Cmd+K palette animation and gave a priority list. Nothing was run or rendered; the suggested values come from the Skill's own tables.

What it does

Encodes a working designer-engineer's philosophy of UI polish. It asks the animation questions in order: should this animate at all (never on keyboard-driven actions used hundreds of times a day), what is the purpose, which easing, how fast. It specifies strong custom curves instead of the built-in CSS ones, keeps UI under about 300ms, and explains when to reach for springs. It covers component details (press feedback, never scaling from zero, origin-aware popovers, tooltip delay rules, transitions over keyframes), clip-path tricks, drag and gesture behavior, performance rules (animate transform and opacity only), reduced motion and touch-hover gating. Reviews must come back as a Before / After / Why table with a checklist.

Output

A review table, a priority list and suggested CSS or JSX with the reasoning behind each value.

Good for

Front-end developers and designers who want their interfaces to feel right, and teams who want a consistent bar for reviewing motion and component polish.

Notes & risks

Low risk: pure instructions with no scripts, no network access and no file writes. It gives opinionated front-end advice; the numbers come from the author's own tables, so treat them as strong defaults, not laws. Test-run once on a pasted snippet: it returned the required table, flagged a command-palette animation and gave priorities. Nothing was run or rendered. When invoked with no question it replies only with a short greeting. By Emil Kowalski, MIT.

SKILL.md original2 files in this package