Skip to content

SVG Illustration

A UI principle for coding agents. Also covers illustration, gradients, svg depth, glossy, skeuomorphic, empty state art, and 1 more.

Show all 7 aliases

illustration, gradients, svg depth, glossy, skeuomorphic, empty state art, vessel

An illustration reads as an object, not a diagram, when light behaves consistently across it. That is a small set of construction rules, not artistic talent: gradients that model a light source, a shadow that seats the object, and layers ordered so things can sit inside other things.

Model Light With Gradients, Not Flat Fills

Pick one light direction and keep it for the whole scene. Every surface then follows the same logic: faces toward the light are lighter, faces away are darker.

  • A rounded surface such as a cylinder is light at the edges nearest the light, darkest around the middle of its curve away from it. Five stops across a horizontal gradient sell the roundness: mid, light, mid-dark, light, dark
  • A flat surface gets a simple two-stop gradient, top lighter than bottom
  • An opening shows the inside, which faces away from the light: fill it with a darker two-stop gradient than any outer surface
<linearGradient id="body" x1="0" y1="0" x2="1" y2="0">
  <stop offset="0"    stop-color="oklch(0.82 0.01 260)"/>
  <stop offset="0.18" stop-color="oklch(0.97 0.005 260)"/>
  <stop offset="0.5"  stop-color="oklch(0.89 0.008 260)"/>
  <stop offset="0.82" stop-color="oklch(0.96 0.005 260)"/>
  <stop offset="1"    stop-color="oklch(0.78 0.012 260)"/>
</linearGradient>

Derive the stops from one base color by moving lightness, so the object stays on brand: the base is the mid stop, highlights raise lightness, shade lowers it.

Know Which Space a Gradient Lives In

gradientUnits decides what the gradient's coordinates mean. The default, objectBoundingBox, maps 0 to 1 across the box of the shape using it, so one gradient stretches to fit anything. userSpaceOnUse positions the gradient in viewBox coordinates, so several shapes can share a single light source at a fixed place.

  • Use the default for self-contained parts, such as a body or a lid
  • Use userSpaceOnUse when a highlight must run continuously across separate shapes
  • gradientTransform rotates or shifts a gradient without editing its stops, for example gradientTransform="rotate(90)" to turn a horizontal blend vertical

Seat the Object With a Floor Shadow

An object without a contact shadow floats. A radial gradient in an ellipse under the base is enough, darkest in the centre and fading to transparent.

<radialGradient id="floor" cx="0.5" cy="0.5" r="0.5">
  <stop offset="0"   stop-color="oklch(0 0 0 / 0.22)"/>
  <stop offset="0.7" stop-color="oklch(0 0 0 / 0.08)"/>
  <stop offset="1"   stop-color="oklch(0 0 0 / 0)"/>
</radialGradient>
<ellipse cx="90" cy="192" rx="62" ry="12" fill="url(#floor)"/>

Keep it wider than the base and thin. A shadow taller than about a fifth of its width reads as a hole.

Split Vessels Into Back and Front

Anything that holds something is drawn as two pieces: the back half (inside wall, rear rim) and the front half (front wall). Content renders between them, so it is overlapped by the front wall and reads as being inside.

<VesselBack/>     bottom layer
{contents}        middle
<VesselFront/>    top layer

This is what makes drop-into-container animations work: the arriving item passes behind the front layer. Draw every container this way even when nothing animates yet, so the option stays open.

A rim is two layers, not one. Any stroke or lip that circles an opening must split the same way: its back arc renders behind the arriving item, its front arc in front. Drawn as a single full ellipse on the front layer, the back arc paints across anything entering the mouth, and the item reads as clipping through solid material.

back layer:   M42 80 A54 14 0 0 1 150 80    the far arc
front layer:  M42 80 A54 14 0 0 0 150 80    the near arc

Contents lay out from the mouth, never from fixed offsets. When several items land inside a vessel, compute their positions from the opening's centre, the item size, and the current count, tightening spacing as the count grows. A fixed left edge with fixed spacing works for the count you tested and pushes later items through the wall. Walls also slope, so the usable width at sinking depth is narrower than at the rim.

Give arrivals a rest beat. An item that fades the moment it lands reads as deleted. Let it sit at the mouth for a beat, then let the vessel swallow it by occlusion, sinking behind the front layer, not by fading its opacity. Disappearance should come from being covered, because that is how objects vanish into containers in the world.

Masks Cut Softly, Clips Cut Hard

A clip-path is binary: inside or outside. A mask uses luminance, where white areas show the content, black hides it, and greys are partial, so a mask can fade an edge.

  • Mask geometry attributes default to objectBoundingBox units; content coordinates inside the mask are user space, so draw the mask shapes in the same coordinates as the artwork
  • A gradient inside a mask fades content out along one direction, which is how a reflection dissolves
  • Every mask, gradient, and filter is referenced by id: ids must be unique per page, so namespace them per component instance in React

Filters Add Atmosphere, Sparingly

feGaussianBlur with a small stdDeviation softens a shadow or glow; the blur radius grows with the value. Larger blurs cost real rendering time, so keep them for one or two elements, not every shape. A blur pushed through feColorMatrix with raised alpha contrast merges nearby shapes into one blobby silhouette, the goo effect used for liquid metaballs.

Skip filters entirely on small icons. At icon sizes a blur reads as smudge.

Compose in a Consistent Order

  1. Floor shadow
  2. Back layers of vessels, including the rear arc of any rim
  3. Contents, or an arriving item
  4. The object's body, darkest surfaces first
  5. Highlights: a soft white vertical strip at low opacity sells gloss on any rounded body
  6. Front layers of vessels, ending with the front arc of the rim

Rim strokes use the same gradient family as the body, but they do not all belong at the end: the rear arc sits at step 2 and only the front arc closes the stack. Putting every stroke last is what makes an arriving item appear to cut through the rim.

Group each object in a <g> with a readable id, so an animation can later target the whole object or a single layer.

Match the Design System

An illustration in a product carries the product's palette. Take the base hues from the brand tokens, build the light and dark stops by adjusting lightness of those hues, and reuse the product's radius character: a soft rounded brand gets rounded corners on its objects, a sharp technical brand gets crisper geometry.

Checklist

  • One light direction across the whole scene
  • Rounded surfaces use multi-stop gradients, not flat fills
  • The object has a contact shadow seating it on the ground
  • Containers are split into back and front layers, with the rim split into rear and front arcs
  • Gradient and mask ids are namespaced for reuse
  • Filters are limited to a few elements and skipped at icon size
  • Colors derive from the design system's tokens

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-ui-principle({ topic: "svg-illustration" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems