# Expanding actions button

> One button that opens up to show a couple of extra actions, with the plus turning into a cross.

- Category: [Buttons](https://library.aimran.es/components/buttons)
- Built with: React + Motion
- Live editor: https://library.aimran.es/blocks/expanding-menu-button
- Library: AIMRAN Library (https://library.aimran.es), free, no sign-up, by Aimran Oukham

## Where to use it

Not every action deserves its own button. This one keeps secondary options like “Save” and “Share” tucked away until someone asks for them, which is handy on a recipe card, a product page in a shop or a document in a client portal. Think of it as a compact expandable action menu in React.

Two or three actions is the sweet spot; beyond that, a proper dropdown menu is easier to scan. Give the main label a verb people understand, like “Options” or “More”, instead of an icon on its own.

## Code

### React (Motion)

```tsx
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { ChevronDown, Plus, Share2 } from "lucide-react";

interface ExpandingMenuButtonProps {
  text?: string;
}

export default function ExpandingMenuButton({
  text = "Actions",
}: ExpandingMenuButtonProps) {
  const [open, setOpen] = useState(false);

  return (
    <motion.div
      layout
      transition={{ type: "spring", stiffness: 300, damping: 30 }}
      className="overflow-hidden rounded-[26px] bg-white dark:bg-[#1c1c1e] p-1 text-[#1d1d1f] dark:text-[#f5f5f7] shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_28px_rgba(0,0,0,0.05)]"
    >
      <button
        type="button"
        aria-expanded={open}
        onClick={() => setOpen((v) => !v)}
        className="flex h-12 items-center gap-2 rounded-full px-4 text-sm font-medium"
      >
        <Plus className={`h-4 w-4 transition-transform ${open ? "rotate-45" : ""}`} />
        {text}
        <ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
      </button>
      <AnimatePresence>
        {open && (
          <motion.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            className="flex gap-1 overflow-hidden px-1 pb-1"
          >
            <button
              type="button"
              className="rounded-full bg-black/5 px-4 py-2 text-sm dark:bg-white/10"
            >
              Save
            </button>
            <button
              type="button"
              className="flex items-center gap-1 rounded-full bg-black/5 px-4 py-2 text-sm dark:bg-white/10"
            >
              <Share2 className="h-4 w-4" /> Share
            </button>
          </motion.div>
        )}
      </AnimatePresence>
    </motion.div>
  );
}
```

## Prompt for AI assistants

Paste this into ChatGPT, Claude, Cursor, v0 or Lovable to adapt the block to your stack (Tailwind, Vue, Svelte…):

````text
I want to add the “Expanding actions button” block (Buttons) from AIMRAN Library to my project.

## What it is
One button that opens up to show a couple of extra actions, with the plus turning into a cross.

## Chosen settings
- Text: "Actions"

## What I need
1. Add it to my project with the same look and animation as the reference code: same sizes, colours, radii, shadows, timings and easing.
2. It is built with React + TypeScript, with framer-motion for the animation and lucide-react for the icons. If my project uses something else (Vue, Svelte, Astro, plain HTML…), translate it without losing any behaviour. If I use Tailwind, turn the styles into classes; if not, keep the CSS in its own file.
3. Expose the settings above as props (or CSS variables) with those values as defaults.
4. Make it work well on phones: nothing that depends only on hover, touch targets of at least 44 px and no overflow on 320 px screens.
5. Make it look right in light and dark mode.
6. Keep it accessible: semantic HTML, roles and aria-* where needed, visible focus and keyboard support.
7. Respect prefers-reduced-motion: with reduced motion, no big animations but the same end result.

## Reference code
```tsx
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { ChevronDown, Plus, Share2 } from "lucide-react";

interface ExpandingMenuButtonProps {
  text?: string;
}

export default function ExpandingMenuButton({
  text = "Actions",
}: ExpandingMenuButtonProps) {
  const [open, setOpen] = useState(false);

  return (
    <motion.div
      layout
      transition={{ type: "spring", stiffness: 300, damping: 30 }}
      className="overflow-hidden rounded-[26px] bg-white dark:bg-[#1c1c1e] p-1 text-[#1d1d1f] dark:text-[#f5f5f7] shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_28px_rgba(0,0,0,0.05)]"
    >
      <button
        type="button"
        aria-expanded={open}
        onClick={() => setOpen((v) => !v)}
        className="flex h-12 items-center gap-2 rounded-full px-4 text-sm font-medium"
      >
        <Plus className={`h-4 w-4 transition-transform ${open ? "rotate-45" : ""}`} />
        {text}
        <ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
      </button>
      <AnimatePresence>
        {open && (
          <motion.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            className="flex gap-1 overflow-hidden px-1 pb-1"
          >
            <button
              type="button"
              className="rounded-full bg-black/5 px-4 py-2 text-sm dark:bg-white/10"
            >
              Save
            </button>
            <button
              type="button"
              className="flex items-center gap-1 rounded-full bg-black/5 px-4 py-2 text-sm dark:bg-white/10"
            >
              <Share2 className="h-4 w-4" /> Share
            </button>
          </motion.div>
        )}
      </AnimatePresence>
    </motion.div>
  );
}
```

Give me the complete final code, ready to paste, and tell me in one line where to put it in my project.
````
