Making a slide down menu

Viewed 29

I am making a slide down menu, but it just jumps to its intended height, with no transition. Can anyone point me in the right direction?

const Container = styled.section`
  position: relative;
  overflow: hidden;
`

const WrapperItems = styled.div`
  padding-left: 2rem;
  user-select: none;
  transition: all 1s ease-in-out;
  position: relative;
  height: 0;

  &.active {
    max-height: 100vh;
    height: auto;
  }

return (
    <Container>
      <Wrapper onClick={() => setIsOpen(!isOpen)}>
        <div className="title">{title}</div>

      </Wrapper>
      <WrapperItems className={isOpen ? "active" : ""}>
        {items.map(item => (
          <div className="item" onClick={() => navigate(item.to)}>{item.title}</div>
        ))}
      </WrapperItems>
    </Container>
  );
1 Answers

CSS solutions

Unfortunately there is no way to make a transition on an auto value in CSS. This choice has been made because it was considered as too expensive.

It will work if you replace the auto by a fixed value like 500px.

To avoid fixing exactly the content, you could also use a "hack" by doing the transition on max-height, and choosing to animate to a value that you know is always higher than the value of your content. But you will have to choose a value that is high enough, but not too high because there will be a delay where the animation will start collapsing the non visible part of the items wrapper.

padding-left: 2rem;
user-select: none;
transition: all 1s ease-in-out;
position: relative;
max-height: 0;

&.active {
  max-height: 30vh; /* you know for sure that your content will never exceed 30 vh*/
}

JavaScript solutions

You have also JavaScript based solutions that work well.

Here is an example with framer motion that lets you use the height: auto syntax :

import { motion } from "framer-motion";

// [...]

return (
  <Container>
    <Wrapper onClick={() => setIsOpen(!isOpen)}>
      <div className="title">{title}</div>
    </Wrapper>
    <WrapperItems>
      <motion.div
        animate={isOpen ? "open" : "closed"}
        variants={{
          open: { height: "auto" },
          closed: { height: 0 },
        }}
      >
        {items.map((item) => (
          <div className="item" onClick={() => navigate(item.to)}>
            {item.title}
          </div>
        ))}
      </motion.div>
    </WrapperItems>
  </Container>
);

// [...]

Related