# react-animate-height animates a React element with plain CSS transitions, no animation library

> react-animate-height is a dependency-free React component that slides an element up and down, or to any specific height, using CSS transitions and a set of state class names you can style yourself. Version 3 rewrote it with hooks and renamed the animation callbacks, and it deliberately stays out of your layout.

**Stanko/react-animate-height** — Lightweight React component for animating height using CSS transitions. Slide up/down the element, and animate it to any specific height.

- Repository: https://github.com/Stanko/react-animate-height
- Website: https://muffinman.io/react-animate-height
- Stars: 760 · Forks: 49
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/stanko-react-animate-height

## Height is animated with CSS transitions, not with a runtime

react-animate-height is a React component for animating height using CSS transitions, with no dependencies. The description is precise about scope: slide an element up and down, or animate it to any specific height.

There is no animation runtime here, no requestAnimationFrame loop, no keyframe system and no dependency to install. The component measures the content, sets an explicit pixel height, and lets the browser's CSS transition engine do the interpolation. That is why the package is described as lightweight and why the README highlights the absence of dependencies so prominently.

The practical benefit is that the animation inherits the browser's own performance characteristics, and you control the timing and the easing through your stylesheet rather than through component props. The component exposes duration, delay and easing as props, but they map onto the CSS transition rather than driving a JavaScript animation.

Content opacity can be animated at the same time if you want it, through the animateOpacity prop, and the component applies CSS class names for each animation state so you can style the transitions yourself.

The project is at version 3.2.4 in the package metadata, is MIT licensed, and the branch structure in the repository is unusual in a useful way: the default branch is v3, with the older v2 kept on its own branch.

## The height prop takes 0, auto or a number, and the demo shows the toggle

The quick start in the README is a complete accordion in about twenty lines. You keep the height in state, and the button flips between 0 and auto:

```jsx
import React, { useState } from 'react';
import AnimateHeight from 'react-animate-height';

const Example = () => {
  const [height, setHeight] = useState(0);

  return (
    <div>
      <button
        aria-expanded={height !== 0}
        aria-controls="example-panel"
        onClick={() => setHeight(height === 0 ? 'auto' : 0)}
      >
        {height === 0 ? 'Open' : 'Close'}
      </button>

      <AnimateHeight
        id="example-panel"
        duration={500}
        height={height} // see props documentation below
      >
        <h1>Your content goes here</h1>
        <p>Put as many React or HTML components here.</p>
      </AnimateHeight>
    </div>
  );
};
```

The prop contract is simple. The height prop is required and takes a numeric or percentage value, for example '50%', or 'auto'. To slide up you pass 0, to slide down you pass 'auto', and when it changes the element height is animated to that value.

The other timing props are duration in milliseconds with a default of 500, delay in milliseconds with a default of 0, and easing, a CSS easing function string, defaulting to 'ease'.

Note the accessibility attributes in the example, because the README makes them part of the contract rather than an afterthought. The button carries aria-expanded reflecting whether the height is non-zero and aria-controls pointing at the panel id, and the panel id is passed through the id prop.

## Version 3 dropped classes for hooks and renamed the callbacks

The v3 branch is the current line, and it is a rewrite rather than an increment. The README states that this is the version 3.x branch, rewritten to hooks, which means you need React 16.8 or newer. Version 2 lives on its own v2 branch.

The breaking change is in the callback names, and the reason given is to avoid clashing with the native ones. onAnimationStart became onHeightAnimationStart, and onAnimationEnd became onHeightAnimationEnd.

That reason is worth noting, because it is a small design argument about a real problem. onAnimationStart and onAnimationEnd are the names the browser uses for the Web Animations API, and a component that occupies those names on the same element makes it harder to tell which event you are handling. Prefixing them with the height is the component's way of staying out of the way.

The two callbacks receive an object as their first argument containing newHeight, the pixel value of the height at which the animation will end for the start callback, and the pixel value at which it ended for the end callback. So the end callback gives you the measured height, which is what you need if you want to know how tall the panel actually ended up rather than what you asked for, particularly with 'auto'.

The peer dependency range in the package metadata matches the constraint, with react and react-dom at >=16.8.0.

## Eleven state class names you can style

The component applies CSS classes for each animation state, and the animationStateClasses prop lets you override them. The defaults are a complete vocabulary of what the component is doing:

```
  {
    animating:                  'rah-animating',
    animatingUp:                'rah-animating--up',
    animatingDown:              'rah-animating--down',
    static:                     'rah-static',
    animatingToHeightZero:      'rah-animating--to-height-zero',
    animatingToHeightAuto:      'rah-animating--to-height-auto',
    animatingToHeightSpecific:  'rah-animating--to-height-specific',
    staticHeightZero:           'rah-static--height-zero',
    staticHeightAuto:           'rah-static--height-auto',
    staticHeightSpecific:       'rah-static--height-specific',
  }
```

The naming separates two axes. The first is whether the element is animating or static, and the second is which direction it is going, up or down, and which kind of target it is heading for, zero, auto or a specific height.

That vocabulary is more useful than it first appears. A stylesheet can give a different transition for the closing direction, or hold the content visible while it collapses, or apply a different easing when the target is auto, all without touching the component.

One caveat the README spells out: this object is merged with the default object and cached when the component is created, so changing it afterwards has no effect. If you need different state classes per instance, that means different component instances rather than a prop change.

## The component will not let you break its height maths

Three props carry a repeated warning, and the warning is the most important thing in the props list.

For className, for style and for contentClassName, the README says you should not apply properties that mess with the layout, like display and height, as these might break height calculations. The style prop goes further: display and height are omitted from the type already, so TypeScript will stop you passing them.

That restriction is the price of the technique. The component measures the content to work out the target pixel height, and it does that by reading the content element. If you set display on the content, the measurement may return nothing, and if you set height on the wrapper, the two fight each other over the same property.

Two related props let you take back control where the defaults are too aggressive. disableDisplayNone defaults to false, and by default the library sets display: none when the height is zero; setting it to true leaves that to you, which the README says is useful when animating to auto height on content change, and it links the relevant issue for details. The README also asks you to be careful not to break accessibility with that prop.

The other default worth knowing is aria-hidden. By default the library sets aria-hidden to true when the height is zero, and you can pass the prop yourself to override it.

## Refs, opacity and the local demo build

Two ref props expose the internals. The ref prop gives you a React.MutableRefObject for the main wrapper div, and contentRef gives you one for the content div, both typed as HTMLDivElement or null. The README shows them being attached with useRef and passed to the component. A wrapper ref is how you would measure or focus something inside a collapsing panel.

The animateOpacity prop, default false, makes the content fade in and out while the height is animated. Since the opacity is a CSS transition as well, it stays in step with the height animation without JavaScript.

The style prop is merged with the inline styles the component sets, so you can add your own styles without losing the transition configuration the component needs. Additional props are passed through to the wrapper div, which the README says makes adding attributes like aria-* easier.

The examples can be run locally. The README gives the two commands, npm install followed by npm start, and then says to open http://127.0.0.1:8000/ in your browser. The package metadata shows what that means under the hood: the start script is an esbuild invocation that bundles the documentation entry point with the demo tsconfig and serves the docs directory, and the build:docs script does the same without serving. There is also a CodeSandbox for the basic example and a separate one built for animating a list, which the README added because people kept asking how to animate list items.

## Dual ESM and CJS builds, Cypress tests and no tagged releases

The packaging is conventional and worth a look if you consume it from a monorepo. The package is an ES module with type set to module, and the exports map offers an import path at ./dist/esm/index.js and a require path at ./dist/cjs/index.cjs, with types at ./dist/esm/index.d.ts. Both builds come from tsc, with the CommonJS build renaming its output to .cjs afterwards.

The scripts reveal the test strategy. The test script runs a server render test and then the main test file, and the Cypress scripts run the end to end suite or open it interactively. A component that measures DOM heights is exactly the kind of thing that needs both a server render check and a real browser check, and having both here is a good sign.

Node 20 or newer is required, according to the engines field, and the repository has an .nvmrc to pin it for development. The repo root also holds several tsconfig files, one per build target plus a base and a demo config, and a CHANGELOG.md and LICENSE.md.

On maintenance, the repository has no GitHub releases, and the last push was on 2026-07-16. The published version in the package metadata is 3.2.4, and the changelog in the repository is the version history to read when you upgrade, particularly around the v3 rewrite.

## Conclusion

react-animate-height fits a React codebase that needs an accordion or a disclosure panel to animate and that does not want to take a full animation library or a CSS-in-JS runtime for it, and it fits teams that want to control the transition timing through their own stylesheet. It is the wrong choice if you need to animate more than height, if your design depends on animating the content's opacity together with layout properties you set yourself, or if you are still on React before 16.8, since version 3 is hooks only. Verify first that the height values you pass are ones the component can measure, that you do not set display or height on the wrapper or content class, and that the aria-hidden and display none defaults suit your accessibility requirements before you disable them.

## FAQ

### How do I use react-animate-height?

Install it with npm install --save react-animate-height, then wrap the content in the component and pass a height prop. Use 0 to slide up, 'auto' to slide down, or a number or percentage for a specific height, and set the duration in milliseconds.

### What changed in version 3 of react-animate-height?

Version 3 was rewritten to hooks, so it requires React 16.8 or newer, and the animation callbacks were renamed to avoid clashing with the native ones: onAnimationStart became onHeightAnimationStart and onAnimationEnd became onHeightAnimationEnd. Version 2 is kept on its own branch.

### How do I style the animation in react-animate-height?

The component applies CSS classes for each animation state, and the animationStateClasses prop overrides them. The defaults cover animating and static states, the up and down directions, and the zero, auto and specific height targets. The object is cached when the component is created, so changing it later has no effect.

### Why does my content disappear when the height is zero?

By default the library sets display: none when the height is zero and also applies aria-hidden to true. You can disable the display none behaviour with the disableDisplayNone prop and handle it yourself, but the README warns to be careful not to break accessibility when you do.

### Can I style the wrapper element without breaking the animation?

Yes, but the README warns not to apply properties that mess with the layout, such as display or height, through className, style or contentClassName, since they can break the height calculations. The style prop already omits display and height from its type.

## Sources

- [Issues](https://github.com/Stanko/react-animate-height/issues)
- [License: MIT](https://github.com/Stanko/react-animate-height/blob/v3/LICENSE)
- [Project website](https://muffinman.io/react-animate-height)
- [README](https://github.com/Stanko/react-animate-height/blob/v3/README.md)
- [Stanko/react-animate-height on GitHub](https://github.com/Stanko/react-animate-height)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/stanko-react-animate-height
