Slide Reveal Icon
An arrow hidden in a zero-width span; on hover, its max-width goes to 24 px, its opacity to 1 and an 8 px gap opens — the button widens by ≈ 18 px to the right in 300 ms, pure CSS.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Buttons has 27 effects, including 2 free. Explore the category →
3 usage examples



How it works
.btn-icon-reveal is a display: inline-flex; align-items: center button with gap: 0 and a padding-right: 20px shorter than the left one (32 px inherited from .demo-btn). Its label is a text node, the icon a <span class="btn-icon">➔</span> placed after it. linear-gradient(135deg, #059669, #10b981) background, white text, transition: .3s on all properties.
At rest, .btn-icon is collapsed: max-width: 0; overflow: hidden; opacity: 0. It takes zero pixels of width and the gap: 0 leaves no space between the text and it: the button seems to hold only a label, slightly off-center to the right (20 px of padding against 32).
On :hover, three values change over .3s: max-width: 24px and opacity: 1 on the icon, gap: 8px and padding-right: 16px on the button. Measured in Chromium: the ➔ arrow measures 14.3 px at 16 px font size, so the button goes from 119.6 to 137.9 px — +8 (gap) +14.3 (icon) −4 (padding) = +18.3 px. The 24 px max-width is only a cap: the real width is the glyph's.
max-width, gap and padding are layout properties: the button really grows and pushes whatever follows it (or re-centers if it is centered). The icon appears sliding in from the left because its container opens left to right — there is no transform.
Accessibility
- Insufficient contrast as shipped: white text on the
#059669 → #10b981gradient = 3.8:1 and 2.5:1, below the 4.5:1 AA threshold — the light half even falls below 3:1. Switch tolinear-gradient(135deg, #065f46, #047857)(7.7:1 and 5.5:1). - Screen readers: the arrow is a text character (
➔, U+2794) and is part of the accessible name — announced “heavy wide-headed rightwards arrow” after the label by VoiceOver. The shipped HTML doesn't hide it: addaria-hidden="true"on.btn-icon. - prefers-reduced-motion not handled: the 18 px widening over 300 ms is a layout movement that displaces neighbors. Add
@media (prefers-reduced-motion: reduce) { .btn-icon-reveal, .btn-icon-reveal .btn-icon { transition: none } }— the icon appears at once. - Keyboard: no
:hoverfor Tab navigation. Duplicate both hover rules under.btn-icon-reveal:focus-visibleand.btn-icon-reveal:focus-visible .btn-icon. The nativeoutlineis not removed and follows the widening. - Touch: on iOS the expanded state sticks after a tap; the arrow stays visible until the next touch (
@media (hover: hover)). Without hover, the icon is never seen on mobile: it must carry no information. Target ≈ 50 px tall, above the 44 px minimum.
Browser compatibility
Requires display: inline-flex with the gap property on a flex container, and transitions of max-width, gap, padding and opacity. No JavaScript, zero dependencies.
Without gap on flex (Safari before 14.1, Chrome before 84), the icon appears glued to the text, without the 8 px space; everything else works. Without transitions, the arrow and the widening pop in at once on hover.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| max-width (CSS — :hover .btn-icon) | 24px | Width cap of the expanded icon. Match it to the real width of the glyph or SVG (14 px for ➔ at 16 px): a value too large lengthens the transition without showing anything. |
| gap (CSS — .btn-icon-reveal 0 / :hover 8px) | 0 → 8px | Text–icon spacing. Its opening is part of the widening; 4px tightens, 12px detaches the arrow from the word. |
| padding-right (CSS — .btn-icon-reveal 20px / :hover 16px) | 20 → 16px | Partial compensation of the widening (−4 px). Use 12px to absorb more, or match the left padding at rest for a centered label. |
| transition (CSS — .btn-icon-reveal and .btn-icon) | .3s | Unfolding duration. .2s = brisk; .45s = slow, the widening then reads as a movement in its own right. |
| opacity (CSS — .btn-icon 0 / :hover 1) | 0 → 1 | Fade of the icon during the opening. Remove the opacity transition for a crisp arrow that “comes out” of the edge, or combine with transform: translateX(-4px) → 0 for a slide. |
| Icon (HTML — span.btn-icon) | ➔ (U+2794) | The glyph depends on the font and the system (sometimes rendered as a color emoji). An inline 16 × 16 px SVG with fill: currentColor is identical everywhere — give it width: 16px; flex: none. |
| background (CSS — .btn-icon-reveal) | linear-gradient(135deg, #059669, #10b981) | Button color. For AA contrast with white text: #065f46 → #047857. |
FAQ
max-width, gap and padding change the layout width, by about +18 px. Three solutions. Place the button at the end of a row (right-aligned) or in a container wider than itself: it grows into empty space. Reserve the room from rest: .btn-icon { max-width: 24px; opacity: 0; transform: translateX(-8px) } with a fixed gap: 8px, then animate only opacity and transform on hover — the arrow slides into place without changing the width. Or set the button's min-width to its expanded width.➔ (U+2794) is a Dingbats character: its shape comes from the system's fallback font, and some mobile browsers render it as a color emoji. Replace it with an inline SVG: <svg class="btn-icon" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M2 8h10M8 4l4 4-4 4" stroke="currentColor" stroke-width="2" fill="none"/></svg>, keeping the .btn-icon rules (max-width, overflow: hidden, opacity) and adding flex: none so it doesn't get squeezed.span.btn-icon before the label in the HTML and swap the paddings: padding-left: 20px at rest, 16px on hover, fixed padding-right: 32px. The container still opens left to right (max-width grows from the span's left edge), so the icon seems to unroll toward the text. For the opposite unrolling, add direction: rtl on the span alone.