Skip to content

New300 animated icons, 75 free. See the icons ›

Nexus UI

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

Animated Git Commit icon for React.

Two line ends run in, a commit node pops onto the line with a ripple, and a green tick draws above it.

Use it forCommit button in a source control panelCommit history listCommit confirmationBranch status in an editor status bar

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

Trigger
Mood
Look
Colour
Stage

What you hear. Two dry ticks and a short sweep as the line ends run in, a round plucked pop with a soft thump as the node lands, a glassy ring, and two small rising notes for the tick drawing in. 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 { GitCommitIcon } from "@/components/icons/git-commit";
 
export default function Page() {
return (
<GitCommitIcon size={104} accent="#30d158" />
);
}

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

Where to use the git commit 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.

  • Commit button in a source control panel

    The commit node pops onto the line on the main button of a git client, and the tick confirms it.

  • Commit history list

    Marks each entry in a commit log next to its message, short hash and author.

  • Committed 3 filesYour message was saved on main.

    Commit confirmation

    Confirms that the staged changes are now a commit in the history.

  • Branch status in an editor status bar

    A small badge beside the branch name that shows work waiting to be pushed.

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/git-commit.tsx, run npm i framer-motion, and import GitCommitIcon.

The Git Commit 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)

Two dry ticks and a short sweep as the line ends run in, a round plucked pop with a soft thump as the node lands, a glassy ring, and two small rising notes for the tick drawing in. 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/git-commit.ts.
  3. Call playIconScore(gitCommit) from a click or hover handler:
"use client";
 
import { GitCommitIcon } from "@/components/icons/git-commit";
import * as gitCommit from "@/components/icons/sounds/git-commit";
import { playIconScore } from "@/components/icons/sound-engine";
 
export function GitCommitButton() {
return (
<button
type="button"
onPointerEnter={() => playIconScore(gitCommit)}
onClick={() => playIconScore(gitCommit)}
>
<GitCommitIcon size={24} />
</button>
);
}

The Git Commit 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 git commit icon

How do I use the git commit icon in React?

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

Where can I use the git commit icon?

commit button in a source control panel, commit history list, commit confirmation, branch status in an editor status bar. Each one is shown live on this page.

Is the git commit icon free?

You can preview and play the git commit icon, with its looks and its sound, for free. Its code and sound file are part of Nexus UI Pro, which unlocks all 225 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 git commit icon make a sound?

Yes, optionally: Two dry ticks and a short sweep as the line ends run in, a round plucked pop with a soft thump as the node lands, a glassy ring, and two small rising notes for the tick drawing in. 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.