Skip to content

Expand Button

A round icon button that grows to show its label when you point at it.

$npx shadcn@latest add https://framebrick.co/r/expand-button.json

From the Compose Digi template, see it in context.

Installation

$ npx shadcn@latest add https://framebrick.co/r/expand-button.json

Usage

import { ExpandButton } from "@/components/ui/expand-button";

<ExpandButton href="https://instagram.com/studio" icon={<InstagramIcon />}>
  Instagram
</ExpandButton>

<ExpandButton size="sm" icon={<ShareIcon />} onClick={share}>
  Share
</ExpandButton>

Props

PropTypeDefaultDescription
iconReactNode—The icon, 16 px: an svg, or a component that takes its size from a size-4 class.
childrenReactNode—The label: shown while the button is open, and the button's accessible name.
size"sm" | "default" | "lg""default"Height, and the closed width, of 32 / 36 / 40 px. Compose Digi's is 32.
hrefstring—Render a Next.js link instead of a button. http(s) links open in a new tab.
…propsbutton / anchor props—Passed to the element (onClick, aria-*, Motion props).
  • It is Compose Digi's social button: a 32 px circle with an outline and a 16 px icon that widens to the icon and its label on hover (32 → 112 px for "Behance"), a fill fading in under it while the icon and the label turn to the fill's text colour at once. One spring drives the width, the padding and the fill — stiffness 500, damping 60, mass 1, over-damped, no overshoot, about 0.65 s — and leaving mirrors it.
  • It opens on a hovering pointer and on keyboard focus (focus-visible only, so a click that leaves focus on it does not hold it open). Touch has no hover: a tap follows the link and the label is never shown, so if the label matters on a phone, show it another way.
  • The label is always in the markup, clipped while the button is closed, so it is what a screen reader reads: there is no second name to keep in step with it. The icon is aria-hidden.
  • The colour rule is only for a pointer that can hover (@media (hover: hover)): a touch screen keeps :hover after a tap and would leave a light icon on a fill that never came.
  • Width and padding are size keys, which <MotionConfig reducedMotion="user"> skips, so with reduced motion it opens at once.
  • Contrast: open, the icon and label are the theme's primary-foreground on its primary, 16:1 or better in the four sample themes, light and dark. For the first few frames of a hover the icon is already light while the fill is still fading in (the fill is at half after about 90 ms): that is the template's own behaviour, kept. tools/qa/axe-themes.mjs --hover cannot read this button, because its fill is a layer inside it, not the button's own background.

AI prompt

Paste into v0, Lovable, Bolt or Claude to rebuild this component in your own project.

Create a React + Tailwind CSS + Motion (motion/react) component called ExpandButton — a round icon button that widens to show its label.
- Props: icon (ReactNode, 16px), children (the label), size sm | default | lg (height and closed width 32 / 36 / 40px), href (a Next.js
  Link via motion.create(Link); external http(s) links open in a new tab with rel noopener noreferrer, otherwise a motion.button), className.
- A rounded-full inline-flex, items-center, gap-2.5, overflow-hidden, whitespace-nowrap, text-sm font-medium, text-foreground. Its
  height and, at rest, its width are the size (a circle); the left and right padding at rest are the 8 / 10 / 12px that centre
  the 16px icon, and 12px open.
- Variants rest and open, animated with one spring (stiffness 500, damping 60, mass 1): the root's width (rest: the size, open:
  "auto"), paddingLeft and paddingRight, and a bg-primary layer behind everything (opacity 0 → 1). The outline is its own absolute
  layer (border border-foreground, rounded-full) so it takes no room.
- open on whileHover and, for the keyboard, while the element has :focus-visible (set a state in onFocus from event.currentTarget.matches(":focus-visible"),
  clear it on blur); initial={false}, so the server renders the closed circle with no flash.
- The icon and the label turn to text-primary-foreground at once on hover (only inside @media (hover: hover), through the
  [@media(hover:hover)]:hover: variant) and on focus-visible. The label is always rendered after the icon, not aria-hidden: it names the button.
- Visible focus ring (ring-[3px] ring-ring/50).

Be the first to know

Get new templates and components first

One short email when we release a template or a free component. No spam — unsubscribe anytime.

We use your email only for Framebrick updates. Privacy policy