Skip to content

Pattern Fills

Ripl supports repeating pattern paint strings directly in fill and stroke properties. A pattern string describes a small square tile (diagonal lines, cross-hatching, dots) that repeats across the shape. The string is parsed at render time and converted to the appropriate native pattern for the current context (Canvas or SVG).

Patterns keep shapes distinguishable without relying on color alone, which helps with accessibility and print-friendly output: two series can share a hue and still read differently.

Demo

NOTE

For the full API, see the Core API Reference.

Pattern Syntax

pattern(<type>[, <foreground>[, <background>[, <size>]]])

Only the type is required; the remaining arguments default sensibly when omitted:

ArgumentDefaultDescription
typerequiredOne of the built-in pattern types below
foreground#000000CSS color used to draw the tile's lines or dots
backgroundtransparentCSS color painted behind the motif (transparent leaves the tile see-through)
size8Width and height of the square repeating tile, in pixels (an optional px suffix is accepted)
ts
// Black diagonal lines on a transparent tile
'pattern(diagonal)';

// Pink dots, transparent background
'pattern(dots, #ff006e)';

// Blue grid on a light background
'pattern(cross-hatch, #3a86ff, #eff6ff)';

// Explicit 12px tile, px suffix optional
'pattern(horizontal, #8338ec, transparent, 12px)';

A string that does not conform to the grammar (unknown type, empty argument, zero or negative size) is not treated as a pattern: parsing fails silently and no pattern is drawn.

Pattern Types

TypeTile
diagonalParallel lines at 45 degrees
cross-hatchA grid of crossing horizontal and vertical lines
dotsA single filled dot centered in the tile
horizontalA horizontal line through the middle of the tile
verticalA vertical line through the middle of the tile

Line thickness and dot radius scale with the tile: lines are drawn at size / 8 pixels thick and dots at a radius of size / 6 pixels, each with a 1 pixel minimum. A larger tile therefore produces a coarser, bolder motif, and a smaller tile a finer one.

Using Patterns

Patterns work anywhere a fill or stroke color is accepted, including both properties at once:

ts
const rect = createRect({
    fill: 'pattern(diagonal, #3a86ff, #eff6ff, 8)',
    stroke: '#1a56db',
    lineWidth: 2,
    x: 50,
    y: 50,
    width: 200,
    height: 120,
});

Because chart options that select colors are ultimately applied as fills and strokes, pattern strings can be used there too, for example as a series color in @ripl/charts.

Patterns tile in user space (aligned to the canvas, not to each shape), so adjacent shapes sharing the same pattern string keep their tiles in phase across the seam.

How It Works

When Ripl encounters a pattern string in a style property:

  1. The string is parsed into a structured Pattern object (type, foreground, background, size)
  2. The shared tile geometry is resolved: the primitive lines and dots that make up one tile, so every context draws the same motif
  3. The context materializes a native pattern:
    • Canvas: draws the tile to an offscreen canvas and creates a repeating CanvasPattern, cached per pattern string
    • SVG: creates a <pattern> element in a <defs> block (tiled in user space) and references it via url(#id), updating the definition in place when the paint changes
  4. The native pattern is applied as the fill or stroke style

Pattern Interpolation

Like gradients, pattern paints animate during transitions. When both values are patterns of the same tile type, renderer.transition() interpolates their foreground color, background color, and tile size:

ts
// A diagonal pattern eases its colors and tile size toward the target.
await renderer.transition(rect, {
    duration: 1000,
    state: {
        fill: 'pattern(diagonal, #ff006e, #fff0, 16)',
    },
});

The tile type is the pattern's signature. When the two patterns share a type they interpolate smoothly; when the types differ (say, diagonal to dots) there is no meaningful in-between motif, so the paint snaps from one to the other at the transition midpoint. The same snap applies when only one endpoint is a pattern, since a repeating tile cannot morph into a plain color or a gradient.

NOTE

The literal transparent keyword is not a parseable color, so a transparent background snaps at the midpoint rather than fading. Use a zero-alpha color such as #fff0 or rgba(255, 255, 255, 0) for a smooth fade.

Working with Patterns Programmatically

The parsing utilities behind pattern strings are exported from @ripl/core:

ts
import {
    isPatternString,
    parsePattern,
    serializePattern,
} from '@ripl/core';

isPatternString('pattern(dots)'); // true

const pattern = parsePattern('pattern(diagonal, #1a6, #fff0, 8)');
// {
//     type: 'diagonal',
//     foreground: '#1a6',
//     background: '#fff0',
//     size: 8,
// }

if (pattern) {
    serializePattern(pattern); // 'pattern(diagonal, #1a6, #fff0, 8)'
}
  • parsePattern(value) parses a pattern string into a Pattern object, applying defaults for omitted arguments, and returns null when the string is invalid.
  • serializePattern(pattern) turns a Pattern object back into its canonical string form.
  • isPatternString(value) is a cheap shape check (does the string look like pattern(...)); use parsePattern to validate fully.
  • PATTERN_TYPES lists the valid PatternType values, useful for building pattern pickers.
  • DEFAULT_PATTERN_FOREGROUND, DEFAULT_PATTERN_BACKGROUND, and DEFAULT_PATTERN_SIZE expose the defaults applied when arguments are omitted.

Custom contexts can call getPatternTileGeometry(pattern) to obtain the renderer-agnostic tile geometry (the tile size plus the lines and dots to draw within it) and reproduce the same motifs in their own backend.