Image Template Layout API: Rects, Lines and Shapes

Image Template Layout API: Rects, Lines and Shapes

An image template layout API is mostly four boring components: rectangle, circle, line and shape. Nobody searches for them and nobody writes about them, and they're the difference between a design that survives real data and one that falls apart the first time a customer has a long name.

Text and images carry the content. These four carry the structure. Here's how they actually behave, including one thing about the line component that isn't obvious from the outside.

The four primitives in an image template layout apiThe four primitives in an image template layout API

ComponentProperties worth knowingReal job
rectfillColor, borderRadius, borderWidth, boxShadow, opacityCards, bands, safe areas behind text
circlefillColor, radius, plus the same border setAvatars, badges, status dots
lineEverything rect hasDividers, rules, underlines
shapepath, viewBox, fillColor, strokeColor, strokeWidthLogos, arrows, stars, any custom vector

All four share a base set: borderWidth, borderColor, borderStyle, borderRadius and opacity. So a "card" is a rect with a radius and a shadow, and a "chip" is the same rect at a smaller size. You don't need a new component type for either.

A line is a rectangleA line is a rectangle

Worth knowing because it saves you asking: the line type renders through the rectangle renderer. A line is a rect with one dimension collapsed.

That's not a shortcut with consequences. It's a useful fact, because everything a rect accepts, a line accepts too: a 2px divider can take a border radius and come out with rounded ends, or an opacity and fade. It can even take a box shadow, though it usually shouldn't.

The useful part is that the mental model is simpler than the component list suggests. Three of the four primitives are boxes with different corner treatments, and the fourth is a vector path.

Layer order is the layoutLayer order is the layout

A design holds an ordered list of components, and they paint in list order. The last one wins the pixel. This is the same painting model the W3C describes for CSS, minus the stacking contexts, so if you've fought with z-index you already know the rules.

In practice it means the order you add things is a design decision, not bookkeeping:

{
  "components": [
    {
      "key": "card",
      "type": "rect",
      "properties": { "fillColor": "#0F172A", "borderRadius": 24 }
    },
    {
      "key": "accent",
      "type": "line",
      "properties": { "fillColor": "#38BDF8" }
    },
    {
      "key": "headline",
      "type": "text",
      "properties": { "text": "Q3 results" }
    }
  ]
}

Move headline above card in that list and the headline disappears behind the card. There's no error and no warning, just a render that looks wrong, so it's the first thing to check when a layer goes missing.

Shape takes any svg path and the path is overridableShape takes any SVG path, and the path is overridable

The shape component isn't a preset picker. It takes raw SVG path data plus the viewBox those coordinates are relative to:

{
  "key": "star",
  "type": "shape",
  "dynamic": true,
  "properties": {
    "path": "M 50 5 L 61 38 L 95 38 L 68 59 L 79 92 L 50 70 L 21 92 L 32 59 L 5 38 L 39 38 Z",
    "viewBox": "0 0 100 100",
    "fillColor": "#F5A623",
    "strokeColor": "#D0021B",
    "strokeWidth": 0
  }
}

Because path is a property like any other, marking it dynamic lets you send a different vector per render:

curl -X POST 'https://render.imejis.io/v1/YOUR_DESIGN_ID' \
  -H 'dma-api-key: YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "star": { "fillColor": "#8B5CF6" } }'

That's the tidy version of a problem people usually solve badly. One design per icon means twelve designs to maintain. One design with an overridable path means one, and preserveAspectRatio keeps the vector from stretching when the box isn't square.

Safe areas are what these are really forSafe areas are what these are really for

A design's width and height are fixed when you build it, and that's what makes the layout predictable. What isn't predictable is the content going into it: a 12-character product name and a 90-character one arrive at the same layer.

The fix is a rect behind the text, sized to the worst case rather than the typical one:

  1. Draw the rect at the full width the text is allowed to occupy.
  2. Put the text layer above it, in the same box, with minFontSize and maxFontSize set so it shrinks instead of overflowing. Text on image covers the auto-resize behaviour in detail.
  3. Give the rect a fill with enough contrast that the text stays readable whatever photo ends up underneath.

Now the longest name in your catalogue lands on a background you designed. Without the rect, it lands on whatever pixel happens to be there.

Mark less than you thinkMark less than you think

Only components marked dynamic can be overridden at render time. For layout primitives, the answer is usually to mark nothing at all.

The rule we'd give: a divider a caller can recolor is a divider that will eventually be recolored badly. Keep fillColor open on the one accent element where per-tenant branding is the point, and leave the rest baked in. Every property you open is a property somebody can get wrong. Dynamic fields has the fuller version of this argument.

Where to go nextWhere to go next

Open a shape component to see the properties with live controls, then check rect, circle and line for the border and shadow options. When the structure holds, add the content layers and render it: the API docs have working calls, and there's a permanent free tier: 100 renders a month, which is plenty for getting a layout right.

Frequently Asked Questions

They're the non-content layers: rectangles, circles, lines and vector shapes. They carry no data of their own. Their job is to divide the canvas, separate sections and give text somewhere safe to sit while the content around it changes per render.

A design is an ordered list of components and they paint in that order, so the last one in the list sits on top. That's the same painting model browsers use, and it's why a background band has to appear before the text it sits behind.

There's a line type, and it renders through the rectangle renderer. A line is a rectangle with one dimension collapsed. Practically that means anything a rect can do, a line can do too: border radius, opacity and a box shadow all apply.

Yes. The shape component takes SVG path data and a viewBox, and both are overridable like any other property when you mark them dynamic. That's how one design renders a different icon or badge outline per call.

Give the text a fixed box and set minFontSize and maxFontSize so it shrinks to fit. Put a rectangle behind it as a safe area, so even the longest string lands on a background you designed rather than on whatever image is underneath.