How do I create striped (zebra) table rows?
Pass template.TableStripe to a table call. gpdf paints the alternate body rows with the color you give it. No row-loop, no manual cell styling.
The question, in other words
I have a table — invoices, transactions, log lines, anything with more than 5 rows — and I want every other row tinted gray so the eye can track across without losing the line. Bootstrap calls it .table-striped. I just want that, in gpdf, without writing a row loop.
TL;DR
c.Table(header, rows, template.TableStripe(pdf.RGBHex(0xF5F5F5)))
That's it. gpdf handles the alternation. The header is excluded — only body rows are striped. The first body row stays plain; the second is tinted; the third plain; the fourth tinted; and so on.
Working code
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
brand := pdf.RGBHex(0x1A237E) // header background
stripe := pdf.RGBHex(0xF5F5F5) // every-other-row tint
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Q1 Sales", template.FontSize(20), template.Bold())
c.Spacer(document.Mm(4))
c.Table(
[]string{"Product", "Region", "Qty", "Revenue"},
[][]string{
{"Laptop Pro 15", "NA", "120", "$155,880"},
{"Wireless Mouse", "EU", "640", "$19,193"},
{"USB-C Hub", "APAC","410", "$20,495"},
{"Monitor 27\"", "NA", "180", "$71,820"},
{"Keyboard", "EU", "320", "$25,596"},
{"Webcam HD", "APAC","260", "$23,397"},
},
template.ColumnWidths(40, 20, 15, 25),
template.TableHeaderStyle(
template.TextColor(pdf.White),
template.BgColor(brand),
),
template.TableStripe(stripe),
)
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("sales.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
go run main.go. Six body rows, three of them tinted, header in dark blue with white text. The kind of report that goes in a Monday email.
How the alternation works
Internally, gpdf walks the body rows with an index i starting at 0 and applies the stripe to rows where i%2 == 1. The header row is its own slice and isn't counted. So:
| Body row index (0-based) | Visually | Striped? |
|---|---|---|
| 0 | 1st | no |
| 1 | 2nd | yes |
| 2 | 3rd | no |
| 3 | 4th | yes |
| ... | ... | ... |
That parity matches the Bootstrap convention. The first row of data sits clean, and the stripe is the visual "rest" — your eye travels across the white row, the next is shaded, repeat.
There's no option to flip the parity (stripe odd-indexed rows instead). If you really want it inverted, prepend an empty row to the body — but you don't, because nobody actually wants that.
Pick the color
The whole point is subtle. A stripe loud enough to compete with the text defeats itself.
pdf.RGBHex(0xF5F5F5) // gentle warm gray — Bootstrap default territory
pdf.RGBHex(0xFAFAFA) // even softer, almost imperceptible at small sizes
pdf.RGBHex(0xEEF2FF) // pale brand tint (works if header is the brand color)
pdf.Gray(0.96) // grayscale equivalent — saves a few bytes in PDF/A workflows
Avoid saturated colors. A blue stripe at 60% saturation reads "this row is selected/important" and breaks the across-row scan that zebra striping is supposed to fix.
For dark themes (rare in PDFs but they exist for slide-style reports), pdf.RGBHex(0x202020) over a 0x1A1A1A page works. Keep the contrast ratio low.
Combine with cell borders
Stripes alone are enough for short tables. For dense, finance-style tables, pair stripes with WithTableCellBorder to draw a hairline between every cell:
hairline := template.Border(
template.BorderWidth(document.Pt(0.5)),
template.BorderColor(pdf.Gray(0.85)),
)
c.Table(header, rows,
template.ColumnWidths(40, 20, 15, 25),
template.TableHeaderStyle(
template.TextColor(pdf.White),
template.BgColor(brand),
),
template.TableStripe(pdf.RGBHex(0xF5F5F5)),
template.WithTableCellBorder(hairline),
)
Hairline borders + light stripe = the look of every accountant's spreadsheet print preview, deliberately. Keep the border lighter than the stripe so the stripe stays the dominant signal.
If you only want an outer frame (no inner grid), swap WithTableCellBorder for WithTableBorder.
Mistakes that cost ten minutes
- Color in the wrong unit range.
pdf.RGB(245, 245, 245)produces a black box. The constructor expects 0.0–1.0, not 0–255. Usepdf.RGBHex(0xF5F5F5)if you're thinking in CSS values. - Striping the header.
TableStripedoes not touch the header. If you want a tinted header, that'sTableHeaderStyle(template.BgColor(...))— a different option. Confusing the two and then wondering why the header isn't tinted is the classic first-time bug. - Two-color alternation. gpdf supports one stripe color, not two. If you set both
pdf.White(row 0) and0xF5F5F5(row 1), you don't actually need to set white — the page is already white. Asking for[white, gray, blue]3-cycle is not a feature; it would also be hostile to the reader. - Stripes on a 3-row table. A stripe needs at least 4–5 body rows to do its job. On 2–3 rows, it just looks like one cell got selected. Skip it; let the table breathe.
Related recipes
- How do I set custom column widths for a table? —
ColumnWidthsin detail. - How do I add a custom TrueType font to gpdf? — make the table render in your brand font.
- Generate an invoice PDF in Go in under 50 lines — a real-world table with header style, stripe, and totals.
Try gpdf
gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, pure-Go TrueType handling.
go get github.com/gpdf-dev/gpdf