The problem
You have a handful of boxes, each with its own shape (photos, video stills, logos, cards, charts), and one container to fit them in. The easy answers each give something up:
- A grid of equal cells crops every box to the cell, or leaves bands of empty space around it.
- Justified rows keep each box’s shape, but only in rows, and the container’s height comes out wherever it lands.
- Equal cells for marks of different shapes look fair and are not: a wide box fills its cell’s width, a tall one only its height, so the wide one gets several times the area.
The idea
Mosaico treats layout as a search over arrangements, and makes the arrangement exact.
treetries every guillotine layout of up to six boxes, where each cut runs all the way across the region it divides. Every node of the tree stores its width as a straight line in its height, so a tile that spans several grid tracks keeps its exact shape and the gutters need no rounding. It keeps the arrangement that leaves the least empty space and crop, with a penalty on any box that would shrink to a sliver.rowsbreaks a list of any length into balanced, centered rows and gives every item the same area, unless that would make it taller than a cap. A wordmark, a badge and a crest then carry the same weight.
Both are pure functions: numbers in, rectangles out, no DOM, deterministic to the byte.
How to use it
import { pack, toGrid } from '@laticent/mosaico';
// A collage: three photos (width / height) in a 16:9 box, with a small gutter.
const collage = pack([1.5, 0.67, 1], { aspect: 16 / 9 }, { gap: 0.012 });
if (collage.ok) {
const { cols, rows, areas } = toGrid(collage); // CSS grid tracks in fr, one area per photo
}
// A logo wall: four marks at equal area, at most four a row.
const wall = pack([4, 1, 0.5, 3], {}, {
strategy: 'rows', gap: 0.05, heightCap: 0.12, maxPerRow: 4,
});
// wall.rects: each mark's box, as fractions of the container's width
The grid adapter emits fr tracks, so the browser fits the result to whatever box it has. Mosaico
needs only the container’s shape.
Spacing, padding and alignment. Give the container a size and every length is in that unit, so a design’s 16 px gutter is
gap: 16 for either strategy. columnGap and rowGap set the two directions apart, padding insets
the layout from the container’s edges, and align says where the layout sits in the space it
leaves: at the start, the center or the end, and for rows justified across the width.
const r = pack([1.5, 0.67, 1], { width: 1280, height: 720 }, {
padding: 24, columnGap: 12, rowGap: 20, align: 'start',
});
// r.placed: each photo's box in pixels, padding and alignment applied, ready to draw
Gutters stay exact even when the layout is too wide for its box and has to shrink, and when fill
stretches it to close the margin: Mosaico resizes the boxes, not the gutters.
Where else it fits
- A photo gallery, a product grid or a dashboard whose tiles arrive in any shape.
- A sponsor or partner page, a “trusted by” strip, a wall of crests or app icons.
- Print layouts, canvas and SVG composition, PowerPoint shapes: the rectangles are plain numbers, and the CSS grid output is only one adapter.
How Lattice uses it
Every collage slide packs its photos with tree. The move into Mosaico made the search about six
times faster (six photos: 58 ms down to 9 ms) without changing one byte of any layout, which a
golden file of 4,128 cases checks. Every logo-wall slide sizes its marks with rows: beside a 1:2 crest,
the wordmarks come down to a square badge’s area, so the crest, held to the row’s height, gets half of
theirs rather than about a quarter. The CLI and the live preview call the same
functions, so both draw the same layout.
What it doesn’t do
- No more than six boxes in
tree. The search grows about eight times per box; past six, userows. - Not ink. Equal area is measured on each box, not on what is drawn inside it. A logo with empty margin in its file looks smaller than its neighbors; crop the file to the logo.
- Not yet: boxes with a range of shapes, panes that grow, or layouts you draw yourself and only want sized. Those are the next strategies in the design note.