Articles

Choosing the right CSS layout

A practical guide to picking the right CSS layout tool for printed and image output, using a product catalogue as the example.

By Palatine · Published · Updated

Pick the layout tool by the job

Say you are building a product catalogue. Each page has a heading, a grid of product cards, and a price table at the bottom. Different parts want different tools. Grid suits the card layout, because you control rows and columns together. Flexbox suits one-dimensional things, like a card with an image, a title, and a price lined up in a row.

ToolGood forAvoid for
GridCards, page skeletons, anything aligned on rows and columns at onceA single row of items of unknown count
FlexboxA row or column of items: a card's contents, a header bar, tagsTwo-dimensional alignment across rows
MulticolumnRunning text that flows from column to columnBoxes that must line up across columns
TableTabular data: prices, specifications, totalsPage layout
FloatText wrapping around an image or a pull quoteBuilding columns

Palatine renders HTML and CSS, but it is not a browser, and you should not assume every CSS feature or edge case behaves exactly as it does in one. Its grid, flexbox, multicolumn and positioning are checked box by box against Chromium, but there are documented gaps: no subgrid, no masonry, no JavaScript. Build the layout, render it, and look. If something is off, simplify the CSS before adding workarounds.

A card grid that fills the page

For the catalogue, let the grid decide how many cards fit across instead of hard-coding it. auto-fill with minmax() gives as many columns as the page width allows, each at least 55 mm wide:

.products {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(55mm, 1fr));
  gap: 6mm;
}

.card {
  display: flex;
  flex-direction: column;
  break-inside: avoid;
}
.card .price { margin-top: auto; }   /* prices line up at the bottom */

Two details do the real work. margin-top: auto on the price pushes it to the bottom of the card, so prices line up across a row even when descriptions differ in length. And in a paged PDF, Palatine breaks a grid between its rows: a row of cards stays together when it fits on a page, and the gap at the break is dropped, so the next page starts cleanly at the top.

Columns and floats

Columns are for text that flows from one column into the next, like a newsletter article. Set the number of columns or a target width, and let the text fill them:

.article {
  columns: 2 70mm;          /* up to 2 columns, each at least 70mm */
  column-gap: 8mm;
  column-rule: 0.5pt solid #ccc;
}
.article h2 { column-span: all; break-after: avoid-column; }
.article figure { break-inside: avoid-column; }

Watch what happens to headings and images that fall at the bottom of a column; a heading alone at the end of a column is a common flaw, and break-after: avoid-column prevents it. Columns are balanced to equal height by default; column-fill: auto fills each one before starting the next. A column box taller than a page continues page after page, with the last row balanced.

Floats still have a job: wrapping text around an image or a pull quote. Do not use them to build page structure when grid or flexbox would do. If text wraps around a float, test a paragraph that is shorter than the float, since the next element may behave differently from what you expect: a table, an image or a box with overflow: hidden next to a float moves below it when it does not fit beside it, as in a browser.

Figures that go to the top of the page

Print layouts have a tool the web lacks: the page float. A figure with float: top leaves the text flow and goes to the top of the page where it is referenced, or of the next page that has room. The text keeps flowing without a gap where the figure would otherwise not fit.

figure.chart {
  float: top;            /* or bottom */
  width: 100%;
  margin-bottom: 6mm;
}

This only applies to paginated PDF; in the other exports the figure stays in place. It is how magazines and reports keep charts from leaving half-empty pages behind them.

Tables that survive a page break

The price table is the part most likely to cross a page break. Use a real <table> with a <thead>: Palatine breaks it between rows and repeats the header row on every page the table continues on, so a reader on page three still knows which column is which. A <tfoot> repeats the same way, which suits “continued” notes better than grand totals.

table { width: 100%; border-collapse: collapse; }
th, td { padding: 1.5mm 2mm; border-bottom: 0.5pt solid #ddd; }
td.amount { text-align: right; white-space: nowrap; }

Rows stay whole when they fit on a page. Avoid building tables out of grid or flex rows to get repeated headers; they will break, but nothing repeats.

Test with ugly content

Layouts that look fine with tidy sample data often fall apart on real data. Try a product name that is forty characters long, a card with no image, and a price table with a cell that holds three lines. Run them through before you decide the layout works.

Also render at the page sizes you actually ship. A grid with four cards across on A4 may be cramped on a smaller page. And check page breaks: tables and cards that split across two pages are a common problem. Read the render warnings as well: when a box is too tall for a page and has to be cut, Palatine reports it instead of failing silently. Look at the output, adjust, and repeat. A short loop of render and look beats guessing from the CSS alone.

Explore the rendering documentation


All articles