Skip to content

New200 animated icons, 75 free. See the icons ›

Nexus UI

ProPreview and play it free. Its code and sound come with Pro.

Animated Reply icon for React.

A curved arrow that draws itself back and flicks its head as the reply is sent.

Use it forReply to a messageComment threadEmail actionsQuoted message

Plays while the pointer is on the stage. Drag it and let go.

Trigger
Mood
Look
Colour
Stage

What you hear. A backwards whip swish as the arrow draws, a tick as the head flicks, and a soft sent-note. Every note lands on a beat of the animation.

Use it

This is how you would use it. The file itself comes with Pro; the snippet follows everything you change on the stage.

TSXpage.tsx
import { ReplyIcon } from "@/components/icons/reply";
 
export default function Page() {
return (
<ReplyIcon size={104} accent="#bf5af2" />
);
}

Spark just sets the accent prop, so the small details of each icon get their own colour.

Where to use the reply icon

4 real places it fits, each one live. Hover, tap or press a key on a mock-up to see the icon move in context, then copy the file above and drop it in.

  • Reply to a message

    The curved arrow draws itself back, ready to answer.

  • Reply to comment

    Comment thread

    Starts a reply beneath a comment.

  • Email actions

    An option in an email's action menu.

  • Quoted message

    Shows who a draft is answering.

More in context on the Made for real interfaces page: a player, a cart, a toolbar, a chat box and a tab bar.

The code

One file. Save it as components/icons/reply.tsx, run npm i framer-motion, and import ReplyIcon.

The Reply code is part of Pro

Preview and play it above, with its looks and its sound. Pro unlocks the file to copy for every Pro icon.

See plans

The sound (optional)

A backwards whip swish as the arrow draws, a tick as the head flicks, and a soft sent-note. It is written against this icon's own animation: each line below is a note placed on a beat of the picture, and t() stretches it with the speed prop. There are no audio files: it is synthesised in the browser.

  1. Once per project, copy sound-engine.ts and sound-kit.ts from the sound guide into components/icons/.
  2. Save the score below as components/icons/sounds/reply.ts.
  3. Call playIconScore(reply) from a click or hover handler:
"use client";
 
import { ReplyIcon } from "@/components/icons/reply";
import * as reply from "@/components/icons/sounds/reply";
import { playIconScore } from "@/components/icons/sound-engine";
 
export function ReplyButton() {
return (
<button
type="button"
onPointerEnter={() => playIconScore(reply)}
onClick={() => playIconScore(reply)}
>
<ReplyIcon size={24} />
</button>
);
}

The Reply sound is part of Pro

Preview and play it above, with its looks and its sound. Pro unlocks the score to play for every Pro icon.

See plans

Props

PropTypeDefaultWhat it does
sizenumber24Width and height in pixels.
strokeWidthnumber2Line thickness, in a drawing that is 24 units wide.
speednumber1Playback speed: 1 is the designed pace, 2 is twice as fast, 0.5 is half.
accentstringcurrentColorColour of the icon's small accent details (a badge, a spark, a trail). Left out, they match the icon.
classNamestring–Set a text colour here: the icon draws in currentColor.
playboolean–true plays, false rests. Left out, it follows the pointer (or loops, for loaders).

Looks (optional)

Wrap an icon, or a whole grid of them, in IconMaterial to give it a finish: neon glow, an aurora spectrum, polished chrome, or one of the liquid looks (Mercury, Liquid and Glass), which are pure SVG with no canvas or WebGL and each make a sound of their own. Save it as components/icons/icon-material.tsx. Spark needs no helper: it is just the accent prop.

"use client";
 
import { useId, type CSSProperties, type ReactNode } from "react";
import { useReducedMotion } from "framer-motion";
 
export type IconMaterialName = "line" | "spark" | "neon" | "aurora" | "chrome" | "mercury" | "liquid" | "glass";
 
/**
* Looks for the icons: wrap one icon, or a whole grid of them, and they all take on the same finish.
*
* line plain strokes in the current colour (the default; nothing is added)
* spark strokes in the current colour, with each icon's small accent details in colour:
* give the icons an `accent`, e.g. <BellIcon accent="#ff3b30" />
* neon a glow around every stroke, in the current colour
* aurora every stroke painted with a blue-to-orange spectrum
* chrome every stroke painted as polished metal (looks best on a dark background)
* mercury liquid metal: the strokes round and thicken into a lit tube, and a reflected room of light and
* dark bands flows slowly along them (best on a dark background)
* liquid the strokes in the current colour as a wet droplet: where they touch they melt together, with a
* glossy highlight, and the whole icon wobbles like jelly while the pointer is over it
* glass clear glass: a translucent body with a bright edge, a fringe of red and blue where the lens bends
* the light, and a sheen that drifts across (best on a dark or colourful background)
*
* It only restyles, so it works with any icon that draws in currentColor and has a 24-unit viewBox. The three
* liquid looks are pure SVG (gradients and filters): no canvas, no WebGL, nothing to load, and they hold still
* for visitors who have reduced motion switched on.
*/
 
const AURORA: [string, string][] = [["0", "#0090f7"], ["0.3", "#6e5bff"], ["0.55", "#ba62fc"], ["0.78", "#f2416b"], ["1", "#f55600"]];
const CHROME: [string, string][] = [["0", "#f4f5fb"], ["0.22", "#9ea3b6"], ["0.38", "#ffffff"], ["0.55", "#6d7183"], ["0.72", "#e8e9f2"], ["0.84", "#ffe3b8"], ["1", "#bfc3d4"]];
// The room mercury reflects, as one stripe of light: bright sky, soft grey, two hard dark bands and a warm window.
// It starts and ends on the same colour so it can repeat and flow without a seam.
const MERCURY: [string, string][] = [
["0", "#f6f7fc"], ["0.12", "#d6d9e8"], ["0.26", "#8e93aa"], ["0.32", "#262833"], ["0.4", "#a4a9bf"], ["0.52", "#ffffff"],
["0.6", "#646a80"], ["0.65", "#1c1e27"], ["0.73", "#cdd1e3"], ["0.85", "#ffe8c0"], ["1", "#f6f7fc"],
];
const GLASS: [string, string, number][] = [["0", "#ffffff", 0.95], ["0.35", "#e6f0ff", 0.55], ["0.7", "#cfe0ff", 0.3], ["1", "#ffffff", 0.7]];
 
export function IconMaterial({
material = "line",
color,
className,
style,
children,
}: {
material?: IconMaterialName;
/** The current colour for everything inside. Left out, it inherits. */
color?: string;
className?: string;
style?: CSSProperties;
children: ReactNode;
}) {
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
const scope = `nx-mat-${uid}`;
const reduce = useReducedMotion();
const flat = material === "aurora" || material === "chrome";
const stops = material === "aurora" ? AURORA : CHROME;
const liquidish = material === "mercury" || material === "liquid" || material === "glass";
 
// An inline <style> (not a class in a stylesheet), so that url(#…) points into this page's own document.
// Filters go on each top-level shape of an icon (not the <svg>), so their units are the icon's own 24-unit grid
// and a look is the same size-for-size at 16px or 160px.
let css = "";
if (material === "neon") {
css = `.${scope} svg{filter:drop-shadow(0 0 1.5px currentColor) drop-shadow(0 0 6px currentColor)}`;
} else if (flat) {
css =
`.${scope} svg *:not([stroke="none"]){stroke:url(#${scope}) !important}` +
`.${scope} svg *[fill]:not([fill="none"]){fill:url(#${scope}) !important}` +
(material === "chrome" ? `.${scope} svg{filter:drop-shadow(0 1px 1px rgba(0,0,0,.35))}` : "");
} else if (liquidish) {
css =
`.${scope} svg *:not([stroke="none"]){stroke:url(#${scope}) !important}` +
`.${scope} svg *[fill]:not([fill="none"]){fill:url(#${scope}) !important}` +
`.${scope} svg>*:not(defs){filter:url(#${scope}-f)}` +
(material === "liquid" && !reduce ? `.${scope} svg:hover>*:not(defs){filter:url(#${scope}-w)}` : "");
}
 
const flow = !reduce && (material === "mercury" || material === "glass");
 
return (
<div className={[scope, className].filter(Boolean).join(" ")} style={{ color, ...style }} data-material={material}>
{css ? <style>{css}</style> : null}
{flat ? (
<svg width="0" height="0" aria-hidden="true" style={{ position: "absolute" }} focusable="false">
<defs>
<linearGradient id={scope} gradientUnits="userSpaceOnUse" x1="3" y1="21" x2="21" y2="3">
{stops.map(([offset, c]) => (
<stop key={offset} offset={offset} stopColor={c} />
))}
</linearGradient>
</defs>
</svg>
) : null}
 
{material === "mercury" ? (
<svg width="0" height="0" aria-hidden="true" style={{ position: "absolute" }} focusable="false">
<defs>
{/* one stripe of reflected room, repeated, so sliding it by one period loops without a seam */}
<linearGradient id={scope} gradientUnits="userSpaceOnUse" x1="0" y1="24" x2="24" y2="0" spreadMethod="repeat">
{MERCURY.map(([offset, c]) => (
<stop key={offset} offset={offset} stopColor={c} />
))}
{flow ? <animateTransform attributeName="gradientTransform" type="translate" from="0 0" to="24 -24" dur="7s" repeatCount="indefinite" /> : null}
</linearGradient>
<filter id={`${scope}-f`} filterUnits="userSpaceOnUse" x="-4" y="-4" width="32" height="32" colorInterpolationFilters="sRGB">
{/* round the strokes off and let touching ones melt together */}
<feGaussianBlur in="SourceAlpha" stdDeviation="0.5" result="soft" />
<feColorMatrix in="soft" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 -6" result="shape" />
{/* a smooth height map of that shape, lit from the top left like a tube of metal */}
<feGaussianBlur in="shape" stdDeviation="0.65" result="relief" />
<feSpecularLighting in="relief" surfaceScale="2.6" specularConstant="1.2" specularExponent="28" lightingColor="#ffffff" result="shine">
<fePointLight x="3" y="0" z="13" />
</feSpecularLighting>
<feComposite in="shine" in2="shape" operator="in" result="glint" />
<feComposite in="SourceGraphic" in2="shape" operator="in" result="body" />
<feComposite in="body" in2="glint" operator="arithmetic" k1="0" k2="1" k3="0.9" k4="0" result="lit" />
<feDropShadow in="lit" dx="0" dy="0.5" stdDeviation="0.45" floodColor="#000000" floodOpacity="0.42" />
</filter>
</defs>
</svg>
) : null}
 
{material === "liquid" ? (
<svg width="0" height="0" aria-hidden="true" style={{ position: "absolute" }} focusable="false">
<defs>
<linearGradient id={scope} gradientUnits="userSpaceOnUse" x1="3" y1="3" x2="21" y2="21">
<stop offset="0" stopColor="currentColor" style={{ stopColor: "color-mix(in srgb, currentColor 55%, white)" }} />
<stop offset="0.5" stopColor="currentColor" />
<stop offset="1" stopColor="currentColor" style={{ stopColor: "color-mix(in srgb, currentColor 70%, black)" }} />
</linearGradient>
<filter id={`${scope}-f`} filterUnits="userSpaceOnUse" x="-4" y="-4" width="32" height="32" colorInterpolationFilters="sRGB">
<LiquidCore wobble={false} />
</filter>
<filter id={`${scope}-w`} filterUnits="userSpaceOnUse" x="-4" y="-4" width="32" height="32" colorInterpolationFilters="sRGB">
<LiquidCore wobble />
</filter>
</defs>
</svg>
) : null}
 
{material === "glass" ? (
<svg width="0" height="0" aria-hidden="true" style={{ position: "absolute" }} focusable="false">
<defs>
<linearGradient id={scope} gradientUnits="userSpaceOnUse" x1="0" y1="24" x2="24" y2="0" spreadMethod="repeat">
{GLASS.map(([offset, c, o]) => (
<stop key={offset} offset={offset} stopColor={c} stopOpacity={o} />
))}
{flow ? <animateTransform attributeName="gradientTransform" type="translate" from="0 0" to="24 -24" dur="9s" repeatCount="indefinite" /> : null}
</linearGradient>
<filter id={`${scope}-f`} filterUnits="userSpaceOnUse" x="-4" y="-4" width="32" height="32" colorInterpolationFilters="sRGB">
{/* a bright rim where the glass catches the light */}
<feGaussianBlur in="SourceAlpha" stdDeviation="0.3" result="soft" />
<feSpecularLighting in="soft" surfaceScale="2" specularConstant="1" specularExponent="13" lightingColor="#ffffff" result="rim">
<fePointLight x="2" y="0" z="9" />
</feSpecularLighting>
<feComposite in="rim" in2="SourceAlpha" operator="in" result="edge" />
{/* the lens bends red and blue light apart: a fringe on each side of every stroke */}
<feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="red" />
<feOffset in="red" dx="-0.32" dy="0" result="redShift" />
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="green" />
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="blue" />
<feOffset in="blue" dx="0.32" dy="0" result="blueShift" />
<feBlend in="redShift" in2="green" mode="screen" result="rg" />
<feBlend in="rg" in2="blueShift" mode="screen" result="split" />
<feComposite in="edge" in2="split" operator="arithmetic" k1="0" k2="0.9" k3="1" k4="0" result="glass" />
<feDropShadow in="glass" dx="0" dy="0.6" stdDeviation="0.55" floodColor="#0a1030" floodOpacity="0.5" />
</filter>
</defs>
</svg>
) : null}
 
{children}
</div>
);
}
 
/** The shared part of the liquid filter: melt, light, and (on hover) a slow jelly wobble. */
function LiquidCore({ wobble }: { wobble: boolean }) {
return (
<>
{wobble ? (
<>
<feTurbulence type="fractalNoise" baseFrequency="0.05 0.07" numOctaves="1" seed="3" result="noise">
<animate attributeName="baseFrequency" values="0.05 0.07;0.08 0.05;0.05 0.07" dur="2.6s" repeatCount="indefinite" />
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="1.1" xChannelSelector="R" yChannelSelector="G" result="wobbled" />
</>
) : null}
<feGaussianBlur in={wobble ? "wobbled" : "SourceGraphic"} stdDeviation="0.7" result="soft" />
<feColorMatrix in="soft" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 22 -9" result="melted" />
<feGaussianBlur in="melted" stdDeviation="0.7" result="relief" />
<feSpecularLighting in="relief" surfaceScale="3" specularConstant="1.3" specularExponent="18" lightingColor="#ffffff" result="wet">
<fePointLight x="4" y="0" z="10" />
</feSpecularLighting>
<feComposite in="wet" in2="melted" operator="in" result="gloss" />
<feComposite in="melted" in2="gloss" operator="arithmetic" k1="0" k2="1" k3="0.95" k4="0" result="lit" />
<feDropShadow in="lit" dx="0" dy="0.55" stdDeviation="0.5" floodColor="currentColor" floodOpacity="0.45" />
</>
);
}
 

Questions about the reply icon

How do I use the reply icon in React?

Copy reply.tsx into your project, install framer-motion, and import ReplyIcon. Render it like any component: <ReplyIcon size={32} />.

Where can I use the reply icon?

reply to a message, comment thread, email actions, quoted message. Each one is shown live on this page.

Is the reply icon free?

You can preview and play the reply icon, with its looks and its sound, for free. Its code and sound file are part of Nexus UI Pro, which unlocks all 125 Pro icons. 75 other icons are free to copy.

Does it need a library?

Only Framer Motion, which draws the animation. The icon itself is one file of plain SVG and React, with no other dependencies.

Can I change the size and colour?

Yes. Set size (in pixels) and strokeWidth as props. The icon draws in currentColor, so a text colour class on it, or on a parent, changes its colour.

Does the reply icon make a sound?

Yes, optionally: A backwards whip swish as the arrow draws, a tick as the head flicks, and a soft sent-note. The sound is scored against the icon's own animation, so every note lands on a beat of the picture, and it follows the speed prop. It plays only if you call it, and it follows the visitor's sound setting.

What happens with reduced motion?

When the visitor has reduced motion switched on in their system, the icon does not animate. It stays in its resting pose.