# How to Implement Responsive Button Press Feedback with Proper Transform Scale Values

> Implement responsive button press feedback using transform scale with a 160ms ease-out transition for performant accessible interactions. Learn proper scale values.

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

---

**Use `transform: scale(0.97)` with a 160ms ease-out transition on `:active` for performant, accessible press feedback that mimics physical button compression.**

Implementing responsive button press feedback requires balancing visual realism, GPU performance, and accessibility standards. The `emilkowalski/skills` repository codifies a battle-tested pattern for this interaction across its animation standards and design engineering guidelines. This article breaks down the exact values, timing, and implementation from the source code.

## Why Transform Scale Values Matter for Press Feedback

The repository's animation standards specify **scale values between 0.95 and 0.98** for press states. A `scale(0.97)` reduction—approximately 3% shrinkage—creates the visual impression of a physical surface compressing under pressure without triggering layout recalculation or perceptible displacement.

According to [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), the constraints on these values serve multiple purposes:

- **Compositor-only rendering**: `transform` and `opacity` are GPU-accelerated properties that bypass layout and paint phases
- **Physical realism**: Values approaching `scale(0)` would cause elements to disappear entirely, violating material physics
- **Jitter prevention**: Subtle scaling avoids the 1px rounding errors that plague larger transform values on subpixel-positioned elements

## The Canonical CSS Implementation

The repository provides a complete implementation pattern in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md). This CSS follows the standards exactly:

```css
/* Core timing variables */
:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  background: var(--color-primary);
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  /* GPU acceleration hint */
  will-change: transform;
}

/* Press feedback: the critical implementation */
.button:active,
.button:focus-visible {
  transform: scale(0.97);
  transition: transform 160ms var(--ease-out);
}

/* Accessibility: respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
  .button:active,
  .button:focus-visible {
    transition: none;
    transform: none;
  }
}

```

Key implementation details from the source:

- **`:active` pseudo-class**: Targets pointer-down state per [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) guidance to respond immediately rather than on release
- **`:focus-visible` inclusion**: Provides identical feedback for keyboard navigation without showing focus rings on mouse interaction
- **`will-change: transform`**: Signals compositor preparation—use sparingly, only on elements expecting frequent interaction

## Timing and Easing Specifications

The **160ms duration** with an ease-out curve derives from specific performance constraints documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md):

| Parameter | Value | Rationale |
|-----------|-------|-----------|
| Duration | 160ms | Upper bound of 100–160ms "instantaneous" feedback window |
| Easing | `cubic-bezier(0.23, 1, 0.32, 1)` | Deceleration curve—fast start, gentle settle |
| Property | `transform` only | Guarantees 60fps on all target devices |

The ease-out curve produces a perceptually faster response than linear timing while maintaining smooth completion. The repository explicitly prohibits spring physics or bounce effects for press feedback—these introduce latency and feel unresponsive.

## Framework Integration Examples

### React Component

```tsx
// Button.tsx
import './Button.css';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary';
}

export const Button = ({ 
  children, 
  variant = 'primary',
  className = '',
  ...rest 
}: ButtonProps) => (
  <button 
    className={`button button--${variant} ${className}`}
    {...rest}
  >
    {children}
  </button>
);

```

The component delegates all press feedback to CSS, following the repository's separation-of-concerns pattern.

### CSS Custom Properties for Theming

```css
/* Standardized scale tokens per STANDARDS.md */
:root {
  --scale-press: 0.97;
  --scale-press-strong: 0.95;
  --duration-press: 160ms;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}

.button--primary:active {
  transform: scale(var(--scale-press));
  transition: transform var(--duration-press) var(--ease-out);
}

.button--destructive:active {
  /* Stronger feedback for high-consequence actions */
  transform: scale(var(--scale-press-strong));
}

```

## Accessibility Requirements

The repository mandates **dual support** for motion preferences:

1. **Default behavior**: Full animation for standard users
2. **`prefers-reduced-motion: reduce`**: Complete disablement of transforms and transitions

This pattern appears consistently across [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) and [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md). Never reduce to `transform: scale(1)` alone—this still triggers compositor work for no visual benefit.

## Common Implementation Mistakes

Per [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), these anti-patterns flag automated review:

- **Animating on `click` instead of `:active`**: Delays feedback by 50–100ms
- **Using `scale(0)` or `scale(0.5)`**: Violates physical material constraints
- **Transitioning `width`, `height`, or `margin`**: Causes layout thrashing
- **Durations exceeding 200ms**: Perceived as sluggish, not responsive

## Source Files and Standards

| File Path | Purpose |
|-----------|---------|
| [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) | Canonical scale (0.97) and timing (160ms) definitions |
| [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) | CSS implementation reference |
| [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) | Pointer-down response timing guidelines |
| [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) | Automated detection of missing/incorrect press feedback |

## Summary

- **Scale value**: `0.97` provides optimal physical feedback without layout jitter
- **Timing**: 160ms ease-out transition matches human perception of "instantaneous"
- **Trigger**: `:active` pseudo-class ensures immediate pointer-down response
- **Property**: `transform` only maintains 60fps GPU-composited animation
- **Accessibility**: Always pair with `prefers-reduced-motion` media query disablement

## Frequently Asked Questions

### What happens if I use scale(0.9) instead of scale(0.97)?

A 10% reduction feels exaggerated and can cause perceptible layout shift on adjacent elements. The 0.95–0.98 range in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) was determined through device testing to provide unambiguous feedback without visual instability.

### Why 160ms specifically and not 150ms or 200ms?

The 100–160ms window represents the threshold where human perception transitions from "instantaneous" to "animated." The repository standardizes on 160ms as the upper bound to accommodate lower-refresh-rate displays while maintaining responsiveness on 120Hz+ devices.

### Should I animate the release state separately?

No—the repository pattern specifies a single `:active` state with symmetric transition timing. Asymmetrical press/release timing creates cognitive dissonance; the ease-out curve naturally handles both phases when the `:active` state clears.

### How do I test reduced motion support?

Enable "Reduce motion" in system accessibility settings (macOS: System Settings > Accessibility > Display; iOS: Settings > Accessibility > Motion). The button should show no transform change and complete state transitions instantly.