Skip to content

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.

css
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 example4x4 bicubic OKLab fieldmesh-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)
Preview loads when it reaches the viewport.

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.

gradiente integration

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.

ReactMeshGradientPreview.tsx

What A Mesh Gradient Contains

The mesh gradient model has four required parts and one optional patch detail:

Function name`mesh-gradient(...)`. `repeating-mesh-gradient(...)` is intentionally not supported.
Grid config`grid rows columns`, plus optional `method` and `in color-space` interpolation settings.
VerticesNamed points with x/y positions and colors: `vertex v00 0% 0% red`.
PatchesFour vertex references that define one cell: `patch p00 v00 v10 v11 v01`.
HandlesOptional cubic edge metadata attached to patch sides and preserved in the model.

The resolved config looks like this:

ts
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:

ts
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 GradientMesh instance.
  • 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 bilinear and bicubic color 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:

css
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:

css
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 exampleGrid, vertices, patch, method, and interpolationmesh-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)
Preview loads when it reaches the viewport.

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:

txt
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:

css
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 exampleInferred 2x2 gridmesh-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)
Preview loads when it reaches the viewport.

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:

txt
vertices: rows * columns
patches: (rows - 1) * (columns - 1)

For example:

css
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 example3x3 topology with four patchesmesh-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)
Preview loads when it reaches the viewport.

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:

css
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:

css
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 exampleDistorted vertex positionsmesh-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)
Preview loads when it reaches the viewport.

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:

css
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:

txt
top-left -> top-right -> bottom-right -> bottom-left

That 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:

css
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 exampleBilinear patch samplingmesh-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)
Preview loads when it reaches the viewport.

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:

css
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 exampleBicubic patch samplingmesh-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)
Preview loads when it reaches the viewport.

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:

css
grid 2 2 method bilinear in oklab
grid 2 2 method bicubic in oklch longer hue
grid 2 2 method bilinear in hsl decreasing hue

The 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:

css
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 exampleHSL longer hue samplingmesh-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)
Preview loads when it reaches the viewport.

Supported color spaces are:

txt
oklab
lch
oklch
hsl
hwb
lab
srgb
srgb-linear
xyz
display-p3
a98-rgb
prophoto-rgb
rec2020

Hue 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:

css
handle <patch-id> <side> <from-x> <from-y> <to-x> <to-y>

side can be top, right, bottom, or left.

css
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 exampleSerialized patch handlesmesh-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%)
Preview loads when it reaches the viewport.

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:

txt
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.

ts
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',
    },
  },
)
Mesh gradient exampleEquivalent constructor output
Preview loads when it reaches the viewport.

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:

txt
u: 0 left edge, 1 right edge
v: 0 top edge, 1 bottom edge
ts
import { 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 examplePatch color sampling inputmesh-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)
Preview loads when it reaches the viewport.

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.

ts
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 exampleRenderer transformer inputmesh-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)
Preview loads when it reaches the viewport.

The transformer outputs have different shapes:

`css`A CSS background string. For mesh gradients it is a generated SVG data URL because CSS has no native `mesh-gradient()` function.
`canvas-2d`A paint object with `draw(ctx, width, height)`.
`canvas-webgl`A paint object with `draw(canvas, width, height)`.
`svg`An SVG pattern payload with `defs`, `url`, and serialized SVG data.

Normalization

Use format() before storing user input. It parses the string into the internal model and serializes it back to the canonical gradiente string.

ts
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 exampleFormatted user inputmesh-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)
Preview loads when it reaches the viewport.

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:

  • rows and columns must be integers greater than or equal to 2.
  • 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 v00 and v10 must 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:

  1. Start with the smallest grid that can express the surface.
  2. Use bilinear when the shape is simple or interactive.
  3. Use bicubic when smoothness matters more than raw generation cost.
  4. Prefer Canvas 2D or Canvas WebGL for live editors.
  5. Use CSS or SVG output when you need a portable serialized asset.
  6. Cache generated CSS/SVG output if the mesh does not change often.
  7. Use format() before storing user-authored mesh strings.

Practical Checklist

Use this order when building or validating a mesh gradient:

  1. Decide the topology: 2 x 2, 3 x 3, 4 x 4, or larger.
  2. Name vertices with regular ids such as v00, v10, v01, v11.
  3. Place every vertex with x/y length-percentage coordinates.
  4. Give every vertex a valid Culori-readable color.
  5. Create one patch per grid cell.
  6. Write patch references clockwise from top-left.
  7. Choose bilinear for speed or bicubic for smoother surfaces.
  8. Choose interpolation: srgb for simple parity, oklab or oklch for smoother ramps.
  9. Add handles only when your editor or custom renderer needs that metadata.
  10. Use format() before storing user input.
  11. Use transformTo() for renderer output instead of hand-converting the string.