Mesh Gradients
A mesh gradient is the most structural gradient kind in gradiente. Linear, radial, diamond, and conic gradients describe a continuous color ramp with a stop list. A mesh gradient describes a colored surface made from vertices and patches.
Each vertex has an id, an x/y position, and a color. Each patch connects four vertices into one surface cell. The renderer samples colors inside those cells and fills the final rectangle.
mesh-gradient(grid 4 4 method bicubic in oklab, vertex v00 0% 0% hsl(89, 96%, 40%), vertex v10 30.04% 0% #67e8f9, vertex v20 71.53% 0.08% hsl(285, 73%, 66%), vertex v30 100% 1.84% #f472b6, vertex v01 0.62% 38.7% #0f172a, vertex v11 28.18% 35.3% hsl(120, 69%, 63%), vertex v21 66.51% 23.4% #9333ea, vertex v31 100% 37.76% #06b6d4, vertex v02 0% 72.36% #9333ea, vertex v12 30.67% 66.72% hsl(237, 62%, 41%), vertex v22 67.1% 73.74% hsl(111, 79%, 43%), vertex v32 100% 75.98% hsl(240, 95%, 47%), vertex v03 0% 100% #ec4899, vertex v13 26.77% 98.53% #06b6d4, vertex v23 62.17% 99.27% #7c3aed, vertex v33 99.93% 100% #0f172a, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p20 v20 v30 v31 v21, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12, patch p21 v21 v31 v32 v22, patch p02 v02 v12 v13 v03, patch p12 v12 v22 v23 v13, patch p22 v22 v32 v33 v23)mesh-gradient(grid 4 4 method bicubic in oklab, vertex v00 0% 0% hsl(89, 96%, 40%), vertex v10 30.04% 0% #67e8f9, vertex v20 71.53% 0.08% hsl(285, 73%, 66%), vertex v30 100% 1.84% #f472b6, vertex v01 0.62% 38.7% #0f172a, vertex v11 28.18% 35.3% hsl(120, 69%, 63%), vertex v21 66.51% 23.4% #9333ea, vertex v31 100% 37.76% #06b6d4, vertex v02 0% 72.36% #9333ea, vertex v12 30.67% 66.72% hsl(237, 62%, 41%), vertex v22 67.1% 73.74% hsl(111, 79%, 43%), vertex v32 100% 75.98% hsl(240, 95%, 47%), vertex v03 0% 100% #ec4899, vertex v13 26.77% 98.53% #06b6d4, vertex v23 62.17% 99.27% #7c3aed, vertex v33 99.93% 100% #0f172a, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p20 v20 v30 v31 v21, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12, patch p21 v21 v31 v32 v22, patch p02 v02 v12 v13 v03, patch p12 v12 v22 v23 v13, patch p22 v22 v32 v33 v23)mesh-gradient(...) is not a native CSS gradient function. gradiente turns the same internal mesh model into CSS, Canvas 2D, Canvas WebGL, and SVG output. The CSS target is a generated SVG data URL. The SVG target is a pattern payload. The Canvas targets render the sampled mesh directly.
Mesh previews on this page are lazy. A preview does not call parse() or transformTo() until it approaches the viewport, because mesh rendering is much heavier than serializing a normal stop-based gradient.
Use a mesh gradient in your framework
Mesh gradients are not native CSS functions, so transformTo('css') returns a background-ready SVG data URL. The examples use the same heavy mesh string you see above, but the framework code stays the same: parse once, transform to CSS, and mount as backgroundImage.
What A Mesh Gradient Contains
The mesh gradient model has four required parts and one optional patch detail:
The resolved config looks like this:
type GradientMeshConfig = {
rows: number
columns: number
method: 'bilinear' | 'bicubic'
interpolation: {
colorSpace: GradientColorSpace
hue?: GradientHueInterpolation
}
}The data model is not a stop list. A mesh has explicit vertices and explicit patches:
type GradientMeshVertex = {
id: string
x: GradientLengthPercentage
y: GradientLengthPercentage
color: string
}
type GradientMeshPatch = {
id: string
topLeft: string
topRight: string
bottomRight: string
bottomLeft: string
handles?: GradientMeshPatchHandles
}What gradiente Does
For mesh-gradient, gradiente handles the work that native CSS cannot:
- Parses mesh strings into a
GradientMeshinstance. - Infers grid size from regular vertex ids or counts when possible.
- Validates vertex ids, patch ids, colors, coordinates, and topology.
- Stores vertices and patches as the internal source of truth.
- Supports
bilinearandbicubiccolor sampling. - Supports color interpolation through Culori-compatible spaces.
- Preserves optional patch handles in string and JSON serialization.
- Samples colors through
samplePatchColor(patchId, u, v). - Transforms the same model to CSS, Canvas 2D, Canvas WebGL, and SVG.
- Covers the outer render area with edge triangles when vertices do not sit exactly on the rectangle edges.
Anatomy
The full syntax is a comma-separated list of mesh records:
mesh-gradient(
grid <rows> <columns> [method bilinear|bicubic] [in color-space [hue-mode hue]],
vertex <id> <x> <y> <color>,
vertex <id> <x> <y> <color>,
...,
patch <id> <top-left> <top-right> <bottom-right> <bottom-left>,
...,
handle <patch-id> <side> <from-x> <from-y> <to-x> <to-y>
)A minimal explicit mesh has a 2 x 2 vertex grid and one patch:
mesh-gradient(grid 2 2 method bilinear, vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% yellow, vertex v11 100% 100% green, patch p00 v00 v10 v11 v01)mesh-gradient(grid 2 2 method bilinear, vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% yellow, vertex v11 100% 100% green, patch p00 v00 v10 v11 v01)That example contains:
grid 2 2: two rows and two columns of vertices.method bilinear: each patch is sampled by bilinear interpolation.vertex v00 0% 0% red: the top-left vertex is red.vertex v10 100% 0% blue: the top-right vertex is blue.vertex v01 0% 100% yellow: the bottom-left vertex is yellow.vertex v11 100% 100% green: the bottom-right vertex is green.patch p00 v00 v10 v11 v01: one patch connects the four vertices.
Defaults And Inference
The class defaults are:
rows: 2
columns: 2
method: "bilinear"
interpolation.colorSpace: "srgb"grid is still serialized by toString() because mesh topology is too important to hide in canonical output. If the input omits grid, gradiente tries to infer it from vertex ids or counts:
mesh-gradient(vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% red, vertex v11 100% 100% blue, patch p00 v00 v10 v11 v01)mesh-gradient(vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% red, vertex v11 100% 100% blue, patch p00 v00 v10 v11 v01)The canonical string for that input includes the inferred grid 2 2 method bilinear config.
Grid Topology
rows and columns describe topology, not visual alignment. A 3 x 3 grid has nine vertices and four patches:
vertices: rows * columns
patches: (rows - 1) * (columns - 1)For example:
mesh-gradient(grid 3 3 method bilinear in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)mesh-gradient(grid 3 3 method bilinear in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)The ids v00, v10, v20, v01, and so on are not random. gradiente reads them as v<column><row>. That regular id scheme lets the model validate patch adjacency and lets bicubic sampling find neighboring vertices.
Vertices
A vertex record has four parts:
vertex <id> <x> <y> <color>The id must be stable because patches reference it. The x/y values are length-percentage values. The color can be any Culori-readable color string.
Vertices do not have to be visually aligned with the grid. The grid is topological; the actual surface can be distorted by moving vertex positions:
mesh-gradient(grid 2 2 method bilinear in oklab, vertex v00 0% 8% #ff74f6, vertex v10 100% 0% #405de6, vertex v01 12% 100% #fb7655, vertex v11 92% 88% #0f172a, patch p00 v00 v10 v11 v01)mesh-gradient(grid 2 2 method bilinear in oklab, vertex v00 0% 8% #ff74f6, vertex v10 100% 0% #405de6, vertex v01 12% 100% #fb7655, vertex v11 92% 88% #0f172a, patch p00 v00 v10 v11 v01)When vertices do not fully cover the paint rectangle, the built-in renderers add outer edge triangles. That keeps the whole output filled instead of leaving transparent gaps around the mesh.
Patches
A patch record has six parts:
patch <id> <top-left> <top-right> <bottom-right> <bottom-left>The four vertex references must be unique and must exist. For regular ids, patches must describe adjacent grid cells. A 2 x 2 grid needs one patch. A 3 x 3 grid needs four patches. A 4 x 4 grid needs nine patches.
Patch order matters semantically. Write references clockwise from the top-left corner:
top-left -> top-right -> bottom-right -> bottom-leftThat order gives renderers a predictable surface orientation and makes the serialized DSL readable.
Sampling Method
method controls how colors are sampled inside each patch.
bilinear is the default. It blends the top edge, blends the bottom edge, then blends between those two intermediate colors. It is fast, predictable, and good for simple surfaces:
mesh-gradient(grid 3 3 method bilinear in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)mesh-gradient(grid 3 3 method bilinear in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)bicubic samples a smoother surface by looking at neighboring vertices in the regular grid. It is more expensive, but it can remove the visibly planar feel of large bilinear patches:
mesh-gradient(grid 3 3 method bicubic in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)mesh-gradient(grid 3 3 method bicubic in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)Use regular vertex ids for bicubic meshes. If the sampler cannot build a regular vertex grid, it cannot know which neighboring colors belong around a patch.
Color Interpolation
Mesh colors use the same interpolation vocabulary as the other gradient kinds:
grid 2 2 method bilinear in oklab
grid 2 2 method bicubic in oklch longer hue
grid 2 2 method bilinear in hsl decreasing hueThe interpolation clause belongs to grid, not to individual vertices or patches. Every patch in the mesh uses the same color interpolation settings.
Polar color spaces can use hue interpolation modes. This is visible when hue wraps around the color wheel:
mesh-gradient(grid 2 2 method bilinear in hsl longer hue, vertex v00 0% 0% hsl(10, 100%, 50%), vertex v10 100% 0% hsl(350, 100%, 50%), vertex v01 0% 100% hsl(10, 100%, 50%), vertex v11 100% 100% hsl(350, 100%, 50%), patch p00 v00 v10 v11 v01)mesh-gradient(grid 2 2 method bilinear in hsl longer hue, vertex v00 0% 0% hsl(10, 100%, 50%), vertex v10 100% 0% hsl(350, 100%, 50%), vertex v01 0% 100% hsl(10, 100%, 50%), vertex v11 100% 100% hsl(350, 100%, 50%), patch p00 v00 v10 v11 v01)Supported color spaces are:
oklab
lch
oklch
hsl
hwb
lab
srgb
srgb-linear
xyz
display-p3
a98-rgb
prophoto-rgb
rec2020Hue modes are meaningful only for polar color spaces. If a hue mode is supplied for a rectangular space such as oklab, gradiente keeps the color space and serializes the gradient without the hue mode.
Patch Handles
Handles are optional records attached to a patch side:
handle <patch-id> <side> <from-x> <from-y> <to-x> <to-y>side can be top, right, bottom, or left.
mesh-gradient(grid 2 2 method bicubic in oklch longer hue, vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% yellow, vertex v11 100% 100% green, patch p00 v00 v10 v11 v01, handle p00 top 25% 0% 75% 0%, handle p00 right 100% 25% 100% 75%)mesh-gradient(grid 2 2 method bicubic in oklch longer hue, vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% yellow, vertex v11 100% 100% green, patch p00 v00 v10 v11 v01, handle p00 top 25% 0% 75% 0%, handle p00 right 100% 25% 100% 75%)Handles are part of the mesh model, JSON representation, parser, validation, and serializer. The built-in raster renderers sample color from vertices and patches; tools can still use handles to preserve editor-side cubic edge metadata or pass that metadata to a custom renderer.
Programmatic Construction
Most users should start with parse() because it keeps authoring close to the DSL. When you need to build a mesh directly, use GradientMesh.
The constructor takes three parameters:
new GradientMesh(vertices, patches, config?)vertices and patches are required. config is optional. Missing config values are inferred when possible, then resolved from class defaults.
import { GradientMesh } from 'gradiente'
const gradient = new GradientMesh(
[
{
id: 'v00',
x: { kind: 'percent', value: 0 },
y: { kind: 'percent', value: 0 },
color: 'red',
},
{
id: 'v10',
x: { kind: 'percent', value: 100 },
y: { kind: 'percent', value: 0 },
color: 'blue',
},
{
id: 'v01',
x: { kind: 'percent', value: 0 },
y: { kind: 'percent', value: 100 },
color: 'yellow',
},
{
id: 'v11',
x: { kind: 'percent', value: 100 },
y: { kind: 'percent', value: 100 },
color: 'green',
},
],
[
{
id: 'p00',
topLeft: 'v00',
topRight: 'v10',
bottomRight: 'v11',
bottomLeft: 'v01',
},
],
{
method: 'bilinear',
interpolation: {
colorSpace: 'srgb',
},
},
)Sampling A Patch
GradientMesh can sample a color inside a patch without rendering the whole gradient. Use samplePatchColor(patchId, u, v).
u and v are local patch coordinates from 0 to 1:
u: 0 left edge, 1 right edge
v: 0 top edge, 1 bottom edgeimport { GradientMesh } from 'gradiente'
const gradient = GradientMesh.fromString(
'mesh-gradient(grid 2 2 method bilinear in oklab, vertex v00 0% 8% #ff74f6, vertex v10 100% 0% #405de6, vertex v01 12% 100% #fb7655, vertex v11 92% 88% #0f172a, patch p00 v00 v10 v11 v01)'
)
const center = gradient.samplePatchColor('p00', 0.5, 0.5)mesh-gradient(grid 2 2 method bilinear in oklab, vertex v00 0% 8% #ff74f6, vertex v10 100% 0% #405de6, vertex v01 12% 100% #fb7655, vertex v11 92% 88% #0f172a, patch p00 v00 v10 v11 v01)Sampling is useful for editors, color pickers, generated design tokens, tests, and any workflow that needs to inspect the mesh without painting it.
Transforming A Mesh Gradient
Every renderer target receives the same source model. That is the main point of the Core API: parse once, transform many times.
import { parse, transformTo } from 'gradiente'
const gradient = parse(
'mesh-gradient(grid 3 3 method bicubic in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)'
)
const css = transformTo('css', gradient)
const canvas2d = transformTo('canvas-2d', gradient)
const webgl = transformTo('canvas-webgl', gradient)
const svg = transformTo('svg', gradient)mesh-gradient(grid 3 3 method bicubic in oklab, vertex v00 0% 0% #ff00aa, vertex v10 46% 8% #faff00, vertex v20 100% 0% #7c00ff, vertex v01 7% 45% #00c2ff, vertex v11 55% 42% #fff7cc, vertex v21 94% 56% #ff4fd8, vertex v02 0% 100% #00ff7f, vertex v12 48% 93% #00f0ff, vertex v22 100% 100% #005eff, patch p00 v00 v10 v11 v01, patch p10 v10 v20 v21 v11, patch p01 v01 v11 v12 v02, patch p11 v11 v21 v22 v12)The transformer outputs have different shapes:
Normalization
Use format() before storing user input. It parses the string into the internal model and serializes it back to the canonical gradiente string.
import { format } from 'gradiente'
const input = 'mesh-gradient(vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% red, vertex v11 100% 100% blue, patch p00 v00 v10 v11 v01)'
const normalized = format(input)mesh-gradient(vertex v00 0% 0% red, vertex v10 100% 0% blue, vertex v01 0% 100% red, vertex v11 100% 100% blue, patch p00 v00 v10 v11 v01)For mesh gradients, normalization is especially useful because it makes inferred config explicit. That gives editors, snapshots, and stored user input a stable shape.
Validation Rules
Mesh gradients have stricter validation than stop-based gradients:
rowsandcolumnsmust be integers greater than or equal to2.- Vertex count must equal
rows * columns. - Patch count must equal
(rows - 1) * (columns - 1). - Vertex ids and patch ids must be unique.
- Patch references must point to existing vertices.
- Each patch must use four unique vertices.
- Recognized regular ids such as
v00andv10must stay inside the declared grid. - Recognized regular patches must match adjacent grid cells.
- Vertex colors must be readable by Culori.
repeating-mesh-gradient(...)is rejected.
These rules are strict on purpose. A mesh gradient is closer to geometry than to a simple color list, so invalid topology quickly becomes renderer-specific undefined behavior if it is not caught early.
Performance Notes
Mesh rendering is more expensive than normal gradient rendering. The cost depends on the number of patches, the sampling method, and the target.
Use this practical order:
- Start with the smallest grid that can express the surface.
- Use
bilinearwhen the shape is simple or interactive. - Use
bicubicwhen smoothness matters more than raw generation cost. - Prefer Canvas 2D or Canvas WebGL for live editors.
- Use CSS or SVG output when you need a portable serialized asset.
- Cache generated CSS/SVG output if the mesh does not change often.
- Use
format()before storing user-authored mesh strings.
Practical Checklist
Use this order when building or validating a mesh gradient:
- Decide the topology:
2 x 2,3 x 3,4 x 4, or larger. - Name vertices with regular ids such as
v00,v10,v01,v11. - Place every vertex with x/y length-percentage coordinates.
- Give every vertex a valid Culori-readable color.
- Create one patch per grid cell.
- Write patch references clockwise from top-left.
- Choose
bilinearfor speed orbicubicfor smoother surfaces. - Choose interpolation:
srgbfor simple parity,oklaboroklchfor smoother ramps. - Add handles only when your editor or custom renderer needs that metadata.
- Use
format()before storing user input. - Use
transformTo()for renderer output instead of hand-converting the string.