I have not opened a design tool to make a blog cover in months. Every post I publish gets a branded 1000x420 image, and each one is generated by a script that screenshots a styled HTML card with headless Chrome. The cover on this very post was made that way, in about a second, from a title and an accent color.
If you publish anything on a schedule, hand-making cover or social images is exactly the kind of repetitive design work worth deleting. Here is the whole technique.
The trick: headless Chrome screenshots any HTML
You do not need Puppeteer or Playwright for this. The Chrome binary you already have takes a screenshot from the command line, and if you size the page precisely you get an exact-dimension PNG with no cropping:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless --disable-gpu --hide-scrollbars \
--window-size=1000,420 \
--screenshot=cover.png \
card.html
--window-size=1000,420 plus a body sized to exactly 1000x420 gives you a 1000x420 image, every time. That is the entire engine. Everything else is just making card.html look good.
The design lives in CSS
Because the canvas is a web page, the whole look is CSS you already know. Gradients, a faint grid overlay, an accent color, a headline, a footer. Here is a trimmed version of the template:
<!doctype html><meta charset="utf-8">
<style>
* { margin:0; box-sizing:border-box; }
body { width:1000px; height:420px; overflow:hidden;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
.card { width:1000px; height:420px; padding:56px 64px; color:#fff;
display:flex; flex-direction:column; justify-content:space-between;
background:
radial-gradient(1200px 500px at 80% -10%, #38bdf822, transparent 60%),
linear-gradient(135deg,#0a0f1c,#111a2e); }
.kicker { letter-spacing:4px; font-weight:800; color:#38bdf8; }
.title { font-size:52px; line-height:1.08; font-weight:800; letter-spacing:-1px; }
.meta { font-family:ui-monospace,Menlo,monospace; color:#93a4c0; }
</style>
<div class="card">
<div class="kicker">HANDS-ON</div>
<div class="title">Your headline goes here</div>
<div class="meta">whattechpost</div>
</div>
That #38bdf822 is the accent color with a low-opacity hex suffix, which is what gives the top corner its soft glow. Swap the accent per topic and every post stays on-brand while still looking distinct.
Making it parametric
The point is not one card, it is a card for every post from a title and a couple of arguments. So the template has placeholders, and a small Python wrapper fills them, writes a temp HTML file, and shoots it:
tsize = 52 if len(title) < 46 else 44 if len(title) < 64 else 38
doc = TEMPLATE.format(kicker=html.escape(kicker),
title=html.escape(title), accent=accent, tsize=tsize)
with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as f:
f.write(doc); tmp = f.name
subprocess.run([CHROME, "--headless", "--disable-gpu",
f"--screenshot={out}", "--window-size=1000,420", "--hide-scrollbars", tmp],
check=True)
The one detail that earns its keep is tsize: the title font shrinks as the title gets longer, so a short punchy headline fills the card and a long one still fits on three lines instead of overflowing. It is a two-line heuristic that removes the single most annoying manual step.
The gotchas I hit
A few things that are not obvious until they bite:
- HTML-escape the text. A title with an ampersand or an angle bracket will quietly break the layout or the whole render, so run
html.escape()on every value you interpolate. - System fonts render, web fonts need help. The screenshot fires as soon as the page loads, so a
@font-facepulled from the network may not arrive in time. Stick to system fonts, or preload and add a small delay. The system stack above needs nothing. - Size the body, do not crop. Exact dimensions from
--window-sizeplus a body of the same size beats screenshotting something bigger and cropping, with no off-by-a-pixel edges. - Keep
--hide-scrollbarson. Without it, content that is even slightly too tall bakes a scrollbar into the image.
Why this beats a design tool here
For a one-off hero image, open a real design tool. For images you make on every post, this wins on the things that compound: it is versioned in git, it diffs, it regenerates in CI, it never drifts off-brand, and it costs zero minutes per image after the template exists. I changed my accent palette once and every future cover inherited it.
It also pairs with the rest of an automated publishing flow. I wrote yesterday about the walls I hit pushing posts to dev.to through its API; this is the piece that makes each of those posts show up with a cover without me touching a canvas.
If you generate your own social or cover images, I would like to see your template, because the design is the fun part and I am always looking to steal a better gradient.
This article was originally published by DEV Community and written by frank chu.
Read original article on DEV Community