# How to Implement a Collapsible Menu Animation with CSS Transitions in Astro-Big-Doc

> Learn to implement smooth collapsible menu animations using CSS transitions in Astro-Big-Doc. This guide covers max-height transitions and icon rotation for a dynamic user experience.

- Repository: [Micro Web Stacks/astro-big-doc](https://github.com/microwebstacks/astro-big-doc)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Astro-Big-Doc achieves smooth collapsible menu animations by transitioning the `max-height` property from `0` to `200vh` on nested lists while rotating arrow icons via CSS transforms, all controlled by a lightweight JavaScript class toggle.**

The `astro-big-doc` template provides a dependency-free solution for animated navigation menus in Astro-based documentation sites. By combining CSS transitions on `max-height` with minimal JavaScript class manipulation, it creates fluid expand-and-collapse effects without layout thrashing or external animation libraries.

## CSS Foundation: The Max-Height Transition Pattern

The animation engine resides in `src/layout/SubMenu.astro`, where nested unordered lists use a **max-height transition** rather than animating `height` directly. This approach avoids expensive layout calculations while guaranteeing smooth 60fps animations.

The relevant CSS rules define two states for the `ul.nested` class:

```css
ul.nested {
    overflow: hidden;
    max-height: 200vh;                     /* large enough when expanded */
    transition-property: max-height;
    transition-duration: 0.4s;
}
ul.nested.hidden {
    max-height: 0px;                       /* collapsed state */
    transition-property: max-height;
    transition-duration: 0.4s;
}

```

When the `hidden` class is removed, the browser animates `max-height` from `0` to `200vh` over 0.4 seconds, creating a **slide-down effect**. Adding the class reverses the animation for a slide-up collapse. The `200vh` value ensures the menu can accommodate tall content without clipping, while `overflow: hidden` masks the content during the transition.

## JavaScript Toggle Logic

The interactive behavior is handled by [`src/layout/toc_menu_activation.js`](https://github.com/microwebstacks/astro-big-doc/blob/main/src/layout/toc_menu_activation.js). This script registers click listeners on elements with the `expand` class, then toggles CSS classes to trigger the transitions:

```javascript
let toggler = document.getElementsByClassName("expand");
for (let i = 0; i < toggler.length; i++) {
    toggler[i].addEventListener("click", function (e) {
        this.parentElement.parentElement
            .querySelector("ul")?.classList.toggle("hidden");
        this.parentElement.classList.toggle("expanded");
        e.preventDefault();
    });
}

```

The script performs two critical operations on each click:
1. **Toggles `hidden` on the sibling `<ul>`** – This triggers the CSS `max-height` transition, expanding or collapsing the submenu.
2. **Toggles `expanded` on the container** – This activates the arrow rotation animation via CSS, providing visual feedback that the section is open.

## Arrow Rotation Animation

Visual feedback for expandable parent items is implemented through CSS transforms on SVG icons within the `span.icon` elements. The rotation synchronizes with the height transition using identical timing functions:

```css
span.icon > svg {
    rotate: 0deg;
    transition: rotate .4s ease-in-out;
}
.entry_container.parent.expanded > span > svg {
    rotate: 90deg;
}

```

When the `expanded` class is added to the parent container, the arrow rotates 90 degrees clockwise over 0.4 seconds with an ease-in-out curve. This creates a cohesive user experience where the directional indicator animates in lockstep with the submenu expansion.

## Component Structure and Integration

The `SubMenu.astro` component renders hierarchical navigation data and applies the necessary classes for the animation system. Each parent entry receives the `expand` class on its icon container, while nested lists initialize with the `hidden` class to start in a collapsed state:

```astro
---
// src/layout/SubMenu.astro
export interface Props {
    items: Array<Object>;
    root: boolean;
}
const { items, root = true } = Astro.props;
---
{items && (
  <ul class={root ? "root" : "nested hidden"}>
    {items.map(item => (
      <li>
        <div class:list={[
          { entry_container: true, active: item.active, parent: item.parent }
        ]}>
          {item.parent && (
            <span class="icon expand">
              <!-- Arrow SVG -->
            </span>
          )}
          <span class="text">{item.label}</span>
        </div>
        <Astro.self items={item.items} root={false} />
      </li>
    ))}
  </ul>
)}
<style>
  ul.nested {
    overflow: hidden;
    max-height: 200vh;
    transition: max-height 0.4s;
  }
  ul.nested.hidden {
    max-height: 0;
  }
</style>
<script src="./toc_menu_activation.js" />

```

To implement this in your own Astro project, import the component and pass a nested array of menu items:

```astro
---
import SubMenu from '../layout/SubMenu.astro';
import menuData from '../data/menu.json';
---
<SubMenu items={menuData} root={true} />

```

The menu data structure supports recursive nesting through the `items` property, where each parent node can contain child entries that inherit the same collapsible behavior.

## Performance Considerations

Animating `max-height` rather than `height` or `transform` provides specific advantages for documentation navigation:

- **No layout thrashing** – The browser calculates the layout once at the start and end of the transition, not during every frame.
- **GPU acceleration** – Modern browsers promote elements with `transform` properties (like the arrow rotation) to compositor layers, ensuring smooth animations even during page scroll.
- **Content-agnostic** – The `200vh` maximum accommodates dynamic content heights without requiring JavaScript height calculations or ResizeObserver APIs.

## Summary

- **`src/layout/SubMenu.astro`** contains the CSS transition rules using `max-height: 0` to `max-height: 200vh` for smooth slide animations.
- **[`src/layout/toc_menu_activation.js`](https://github.com/microwebstacks/astro-big-doc/blob/main/src/layout/toc_menu_activation.js)** handles click events by toggling the `hidden` and `expanded` classes, triggering both the height transition and arrow rotation.
- **The max-height technique** avoids layout re-flows and works with dynamic content without JavaScript height calculations.
- **Arrow rotation** uses CSS `rotate` transforms with identical duration (0.4s) to synchronize visual feedback with menu expansion.
- **The implementation** requires no external dependencies, making it suitable for lightweight documentation sites built with Astro.

## Frequently Asked Questions

### Why does Astro-Big-Doc use max-height instead of height for the animation?

Using `max-height` allows the menu to animate smoothly without knowing the exact pixel height of the content beforehand. According to the source code in `SubMenu.astro`, setting `max-height: 200vh` accommodates arbitrarily tall submenus while the transition from `0` creates the collapse effect, whereas animating `height` directly would require expensive JavaScript calculations or fixed dimensions.

### How does the arrow rotation stay synchronized with the menu expansion?

The JavaScript in [`toc_menu_activation.js`](https://github.com/microwebstacks/astro-big-doc/blob/main/toc_menu_activation.js) toggles both the `hidden` class on the `<ul>` and the `expanded` class on the parent container simultaneously. The CSS in `SubMenu.astro` applies `transition: rotate .4s ease-in-out` to the arrow SVG and `transition: max-height 0.4s` to the list, ensuring both properties animate over the identical 0.4-second duration.

### Can I customize the animation speed or easing function?

Yes. In `SubMenu.astro`, modify the `transition-duration` values in the `<style>` block (currently set to `0.4s`) and adjust the `transition-timing-function` (currently `ease-in-out` for arrows). The `max-height` value of `200vh` can also be increased if your menus contain exceptionally large nested structures.

### Is this collapsible menu approach accessible for keyboard navigation?

The current implementation in `astro-big-doc` handles click events via the `expand` class listeners. For production use, you should enhance [`toc_menu_activation.js`](https://github.com/microwebstacks/astro-big-doc/blob/main/toc_menu_activation.js) to support keyboard events (Enter and Space keys) on the toggle buttons and add appropriate `aria-expanded` attributes that update when the `hidden` class toggles, ensuring screen readers announce state changes correctly.