Create stunning, animated 3D gradients with hardware-accelerated WebGL performance.
Important
As of v0.7, Neat no longer depends on three.js. The rendering engine has been rewritten to use raw WebGL, reducing the total install footprint from ~653 KB (133 KB gzip) to just 42 KB (12 KB gzip) β an ~93% reduction. Zero external dependencies. If you are upgrading from a previous version, see the Migration Guide below.
β¨ Try the Interactive Editor β¨
Design your perfect gradient with our visual editor, featuring 20+ presets and real-time preview. Export the config and use it in your project instantly.
If you're upgrading from a previous version that used three.js, here's what changed:
| v0.6.x (Three.js) | v0.7 (Pure WebGL) | |
|---|---|---|
| Library bundle | ~42 KB | 42 KB (standalone) |
| Three.js peer dep | 616 KB (122 KB gzip) | 0 |
| Total | ~653 KB (~133 KB gzip) | 42 KB (~12 KB gzip) |
npm uninstall three @types/threeNeat now ships with its own lightweight WebGL renderer β no external 3D library needed.
The mouseDistortionStrength, mouseDistortionRadius, mouseDecayRate, and mouseDarken properties have been removed. You can safely delete them from your config objects β they will be ignored.
All other configuration properties, methods (destroy, downloadAsPNG), and dynamic property setters work exactly the same. Your existing configs from the editor remain fully compatible.
npm install @firecms/neator
yarn add @firecms/neatimport { NeatGradient } from "@firecms/neat";
const gradient = new NeatGradient({
ref: document.getElementById("canvas"),
colors: [
{ color: "#FF5772", enabled: true },
{ color: "#4CB4BB", enabled: true },
{ color: "#FFC600", enabled: true },
{ color: "#8B6AE6", enabled: true },
{ color: "#2E0EC7", enabled: true }
],
speed: 4,
waveAmplitude: 5,
backgroundColor: "#003FFF",
backgroundAlpha: 1
});
// Clean up when done (important for React, Vue, etc.)
gradient.destroy();import { useEffect, useRef } from "react";
import { NeatGradient, NeatConfig } from "@firecms/neat";
function BackgroundGradient() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const gradientRef = useRef<NeatGradient | null>(null);
useEffect(() => {
if (!canvasRef.current) return;
gradientRef.current = new NeatGradient({
ref: canvasRef.current,
colors: [
{ color: "#FF5772", enabled: true },
{ color: "#4CB4BB", enabled: true },
{ color: "#FFC600", enabled: true }
],
speed: 3,
waveAmplitude: 5
});
return () => gradientRef.current?.destroy();
}, []);
return (
<canvas
ref={canvasRef}
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: -1
}}
/>
);
}| Property | Type | Default | Range | Description |
|---|---|---|---|---|
speed |
number |
4 |
0-10 |
Animation speed (0 = static) |
waveAmplitude |
number |
3 |
0-10 |
Wave height intensity |
waveFrequencyX |
number |
5 |
0-10 |
Horizontal wave frequency |
waveFrequencyY |
number |
5 |
0-10 |
Vertical wave frequency |
| Property | Type | Default | Description |
|---|---|---|---|
colors |
NeatColor[] |
Required | Array of color objects (up to 6) |
colorBlending |
number |
5 |
How colors mix together (0-10) |
colorBrightness |
number |
1 |
Overall brightness multiplier |
colorSaturation |
number |
0 |
Color saturation adjustment (-10 to 10) |
horizontalPressure |
number |
3 |
Horizontal color distribution (0-10) |
verticalPressure |
number |
3 |
Vertical color distribution (0-10) |
Color Object:
{
color: string; // Hex color (e.g., "#FF5772")
enabled: boolean; // Toggle color on/off
influence?: number; // Color strength (0-1, optional)
}| Property | Type | Default | Description |
|---|---|---|---|
shadows |
number |
4 |
Shadow intensity (0-10) |
highlights |
number |
4 |
Highlight intensity (0-10) |
grainIntensity |
number |
0.55 |
Film grain amount (0-1) |
grainScale |
number |
2 |
Grain size |
grainSparsity |
number |
0.0 |
Grain distribution sparsity (0-1) |
grainSpeed |
number |
0.1 |
Grain animation speed |
wireframe |
boolean |
false |
Show wireframe mesh |
| Property | Type | Default | Description |
|---|---|---|---|
domainWarpEnabled |
boolean |
false |
Enable domain warping distortion |
domainWarpIntensity |
number |
0.5 |
Strength of domain warping |
domainWarpScale |
number |
1.0 |
Spatial frequency scale of warping |
| Property | Type | Default | Description |
|---|---|---|---|
vignetteIntensity |
number |
0.0 |
Darkness intensity at corners (0-1) |
vignetteRadius |
number |
0.8 |
Radial falloff start distance |
| Property | Type | Default | Description |
|---|---|---|---|
fresnelEnabled |
boolean |
false |
Enable glowing outer edge shader effect |
fresnelPower |
number |
2.0 |
Falloff exponent of the rim glow |
fresnelIntensity |
number |
0.5 |
Brightness of the glow effect |
fresnelColor |
string |
"#FFFFFF" |
Color of the rim glow (hex) |
| Property | Type | Default | Description |
|---|---|---|---|
iridescenceEnabled |
boolean |
false |
Enable soap-bubble style color shifting |
iridescenceIntensity |
number |
0.5 |
Strength of the color shift effect |
iridescenceSpeed |
number |
1.0 |
Color cycle speed |
| Property | Type | Default | Description |
|---|---|---|---|
bloomIntensity |
number |
0.0 |
Intensity of the glow bleeding from highlights |
bloomThreshold |
number |
0.7 |
Brightness threshold for bloom candidate pixels |
| Property | Type | Default | Description |
|---|---|---|---|
chromaticAberration |
number |
0.0 |
Lens color channel splitting distance |
| Property | Type | Default | Description |
|---|---|---|---|
shapeType |
'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon' |
'plane' |
3D shape geometry to render the gradient on |
shapeRotationX |
number |
0 |
Manual X rotation (radians) |
shapeRotationY |
number |
0 |
Manual Y rotation (radians) |
shapeRotationZ |
number |
0 |
Manual Z rotation (radians) |
shapeAutoRotateSpeedX |
number |
0 |
Auto-rotation speed on X-axis |
shapeAutoRotateSpeedY |
number |
0 |
Auto-rotation speed on Y-axis |
sphereRadius |
number |
15 |
Radius of the sphere shape |
torusRadius |
number |
15 |
Torus primary ring radius |
torusTube |
number |
5 |
Torus inner tube thickness |
cylinderRadius |
number |
10 |
Radius of the cylinder shape |
cylinderHeight |
number |
40 |
Height of the cylinder shape |
planeBend |
number |
0 |
Bending distortion applied to the plane geometry |
planeTwist |
number |
0 |
Twisting distortion applied to the plane geometry |
silhouetteFade |
number |
0.25 |
Edge transparency fade for sphere/torus |
cylinderFade |
number |
0.08 |
Transparency fade towards the ends of the cylinder |
ribbonFade |
number |
0.05 |
Transparency fade towards the ends of the ribbon |
| Property | Type | Default | Description |
|---|---|---|---|
cameraLock |
boolean |
false |
Lock camera controls and prevent drag rotation |
cameraX |
number |
0 |
Camera offset along X-axis |
cameraY |
number |
0 |
Camera offset along Y-axis |
cameraZ |
number |
0 |
Camera offset along Z-axis |
cameraRotationX |
number |
0 |
Camera pitch rotation (radians) |
cameraRotationY |
number |
0 |
Camera yaw rotation (radians) |
cameraRotationZ |
number |
0 |
Camera roll rotation (radians) |
cameraZoom |
number |
1.0 |
Camera zoom factor |
| Property | Type | Default | Description |
|---|---|---|---|
backgroundColor |
string |
"#FFFFFF" |
Background color (hex) |
backgroundAlpha |
number |
1 |
Background opacity (0-1) |
| Property | Type | Default | Description |
|---|---|---|---|
resolution |
number |
1 |
Mesh resolution (0.1-2, lower = better performance) |
| Property | Type | Default | Description |
|---|---|---|---|
yOffset |
number |
0 |
Vertical scroll offset |
yOffsetWaveMultiplier |
number |
4 |
How much scroll affects waves (0-20) |
yOffsetColorMultiplier |
number |
4 |
How much scroll affects colors (0-20) |
yOffsetFlowMultiplier |
number |
4 |
How much scroll affects flow field (0-20) |
Example: Parallax Scrolling
window.addEventListener("scroll", () => {
gradient.yOffset = window.scrollY;
});| Property | Type | Default | Description |
|---|---|---|---|
flowEnabled |
boolean |
true |
Enable flow field distortion |
flowDistortionA |
number |
0 |
Primary distortion amplitude |
flowDistortionB |
number |
0 |
Secondary distortion frequency |
flowScale |
number |
1 |
Overall flow field scale |
flowEase |
number |
0 |
Flow field smoothing (0-1) |
| Property | Type | Default | Description |
|---|---|---|---|
enableProceduralTexture |
boolean |
false |
Enable texture overlay |
textureVoidLikelihood |
number |
0.45 |
Gap frequency in texture (0-1) |
textureVoidWidthMin |
number |
200 |
Minimum gap width |
textureVoidWidthMax |
number |
486 |
Maximum gap width |
textureBandDensity |
number |
2.15 |
Texture band density |
textureColorBlending |
number |
0.01 |
Color mixing in texture (0-1) |
textureSeed |
number |
333 |
Random seed for texture |
textureEase |
number |
0.5 |
Flow/Image blend (0=flow, 1=image) |
transparentTextureVoid |
boolean |
false |
Render voids as transparent instead of using proceduralBackgroundColor |
proceduralBackgroundColor |
string |
"#000000" |
Texture void color |
textureShapeTriangles |
number |
20 |
Number of triangle shapes |
textureShapeCircles |
number |
15 |
Number of circle shapes |
textureShapeBars |
number |
15 |
Number of bar shapes |
textureShapeSquiggles |
number |
10 |
Number of squiggle shapes |
Cleans up the WebGL context, event listeners, and removes any injected DOM elements. Call this when the component unmounts to prevent memory leaks (essential for React, Vue, etc.).
gradient.destroy();Triggers a browser download of the current frame as a PNG image.
filename: Optional string for the output file (defaults to"neat.png").
gradient.downloadAsPNG("my-custom-gradient.png");Records the canvas animation and saves it as a high-quality video file (MP4 or WebM depending on browser codec support) with a subtle watermark. Returns a stop function to end recording early.
durationMs: Duration of the recording in milliseconds (default:5000).filename: Output file name (without extension, default:"neat.firecms.co").width: Output video width in pixels (default: current canvas width).height: Output video height in pixels (default: current canvas height).format: Preferred format'mp4'or'webm'(default:'mp4').onProgress: Callback triggered with progress value between0and1.onComplete: Callback triggered when recording successfully finishes.
const stopRecording = gradient.recordVideo({
durationMs: 8000,
filename: "neat-gradient-animation",
format: "mp4",
onProgress: (progress) => {
console.log(`Recording progress: ${Math.round(progress * 100)}%`);
},
onComplete: () => {
console.log("Recording complete!");
}
});
// To stop recording early:
// stopRecording();All properties can be updated in real-time:
// Animation
gradient.speed = 6;
gradient.waveAmplitude = 8;
// Colors
gradient.colors = [
{ color: "#FF0000", enabled: true },
{ color: "#00FF00", enabled: true }
];
// 3D Shape Geometries & Auto-Rotation
gradient.shapeType = "sphere";
gradient.shapeAutoRotateSpeedY = 1.5;
// Advanced Post-Processing Effects
gradient.iridescenceEnabled = true;
gradient.fresnelEnabled = true;
gradient.fresnelColor = "#FF0055";
// Effects
gradient.grainIntensity = 0.5;
// Texture
gradient.enableProceduralTexture = true;
gradient.textureEase = 0.7;-
Lower resolution for better FPS:
resolution: 0.5 // Half resolution = ~4x faster
-
Disable features you don't need:
speed: 0, // Static gradient grainIntensity: 0, // No grain effect flowEnabled: false, // No flow distortion
-
Use fewer colors:
- 3-4 colors = best performance
- 6 colors = more complex but slower
- Subtle animations:
speed: 2-3,waveAmplitude: 3-5 - Dramatic effects:
speed: 5-8,waveAmplitude: 8-10 - Smooth blending: Higher
colorBlendingvalues (7-10) - Sharp contrasts: Lower
colorBlendingvalues (3-5)
Hero Background:
{
colors: [/* your brand colors */],
speed: 3,
waveAmplitude: 5,
shadows: 2,
highlights: 7,
grainIntensity: 0.1
}Subtle Page Background:
{
colors: [/* muted pastels */],
speed: 1,
waveAmplitude: 2,
colorBlending: 9,
backgroundAlpha: 0.7
}Create depth by making different elements move at different speeds:
const gradient = new NeatGradient({
ref: canvas,
colors: [/* ... */],
yOffsetWaveMultiplier: 8, // Waves move faster
yOffsetColorMultiplier: 4, // Colors move slower
yOffsetFlowMultiplier: 6 // Flow in between
});
window.addEventListener("scroll", () => {
gradient.yOffset = window.scrollY;
});Add complex patterns over your gradient:
{
enableProceduralTexture: true,
textureEase: 0.3, // More topographic
textureVoidLikelihood: 0.3, // Fewer gaps
textureBandDensity: 1.5, // Wider bands
textureShapeTriangles: 30, // More shapes
proceduralBackgroundColor: "#000033" // Dark voids
}Full TypeScript definitions included:
import { NeatGradient, NeatConfig, NeatColor, NeatController } from "@firecms/neat";
const config: NeatConfig = {
// ... fully typed config
};
const gradient: NeatController = new NeatGradient(config);Neat uses custom WebGL shaders to render dynamic 3D gradients entirely on the GPU:
- Mesh Generation: Creates a subdivided plane geometry
- Vertex Shader: Displaces vertices to create waves using Perlin noise
- Fragment Shader: Blends colors, applies flow fields, lighting, and effects
- Hardware Acceleration: All computations run on the GPU for smooth 60fps animations
The result is a performant, beautiful gradient that can run on any modern device.
Neat is released under the MIT License + The Commons Clause.
You can:
- β Use freely in personal projects
- β Use freely in commercial projects (e.g. SaaS landing pages, company websites)
- β Modify and redistribute (with attribution)
- β Use in open-source projects
You CANNOT:
- β Sell the software
- β Include it in a paid template or theme builder that you sell
- β Offer the software as a paid service
For commercial projects without attribution, become a sponsor and contact us at hello@firecms.co for a license key.
Created by FireCMS with β€οΈ
Found a bug or have a feature request?
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- π Website & Editor
- π¦ npm Package
- π» GitHub Repository
- π¬ Discord Community
Made with β¨ by the FireCMS team