Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Web Design

Web Design

A Recipe Card That Prints on One Page

Part 6 of the thread Building this notebook

THE SHORT VERSION4 points
  • Recipes here are structured front matter, so the template can draw a proper ingredients-and-directions card with checkboxes.
  • The Print button clones the title, the quick facts and the card into a #print-sheet at the end of body, adds a class, then calls window.print().
  • A print stylesheet hides every other child of body with display: none and tightens the type. afterprint cleans it all up.
  • My first try used visibility: hidden. Hidden things still take up space, so the printout came out five pages long.

Every recipe site has the same problem. You're standing in the kitchen, you hit print, and out comes the header, the navigation, a sidebar, somebody's life story, and somewhere on page three, the ingredients.

The recipes on this site print differently. Hit Print recipe on something like Gram's white layer cake and you get the title, the quick facts, and the card. That's it. Here's how it works, including the version that didn't.

Start with structured data

None of this works if the recipe is just a blob of Markdown. The ingredients and steps live in front matter, grouped:

recipe:
  serves: 16 to 20
  prep: 45 minutes
  oven: 350°F
  ingredients:
    - group: Cake layers
      items:
        - "3 cups all-purpose flour"
        - "8 large egg whites, room temperature"
  steps:
    - group: The cake
      items:
        - "Heat the oven to **350°F**."

Because it's data, the post template can render it however it wants. It draws a recipe card: ingredients on the left, each with a checkbox you can tick off as you go, and numbered directions on the right. The quick facts (servings, prep, cook, heat) sit in a summary box near the top of the post.

The Print buttons ship in the HTML with the hidden attribute. The script un-hides them. No JavaScript, no button, and the browser's own print still works.

The print button builds a sheet

When you click Print, the script doesn't try to hide its way down to the card. It builds a fresh little page and hangs it off the end of body:

const card = document.querySelector('.recipe-card');
if (card) {
  const sheet = document.createElement('div'); sheet.id = 'print-sheet';
  const h = document.createElement('h1'); h.textContent = document.querySelector('.article-header h1')?.textContent || document.title; sheet.append(h);
  const facts = document.querySelector('.recipe-summary dl'); if (facts) sheet.append(facts.cloneNode(true));
  sheet.append(card.cloneNode(true));
  document.body.append(sheet); document.body.classList.add('print-recipe-only');
}
window.print();

Three things go in: the post title, a clone of the quick-facts list, and a clone of the card. Then a class goes on body and the print dialog opens.

Why clone instead of printing the real card? Because the real card lives deep inside the article, inside main, inside the layout. To show it, every one of its ancestors has to stay visible, and then you're back to fighting the whole page. A copy that's a direct child of body has no ancestors worth worrying about.

The print stylesheet hides everything else

With the sheet in place, the CSS is almost boring:

@media print {
  body.print-recipe-only { background: #fff !important; }
  body.print-recipe-only > *:not(#print-sheet) { display: none !important; }
  #print-sheet { display: block; color: #000; font-family: Arial, sans-serif; }
  #print-sheet .script-bar { display: none; }
  #print-sheet .recipe-ingredients input { display: none; }
  #print-sheet .recipe-steps li { font-size: 10pt; line-height: 1.35; padding-bottom: 4px; }
  .recipe-card li { break-inside: avoid; }
}

Every child of body that isn't the sheet gets display: none. The window bar and the checkboxes drop out of the copy. Font sizes switch to points, the padding tightens, and break-inside: avoid keeps a single step from splitting across a page break if a long recipe does spill over.

There's one nice extra. A line with the post's URL sits at the bottom of every card, hidden on screen and shown in print, so a paper copy stuck to the fridge still says where it came from.

Because the hiding rules are keyed to body.print-recipe-only, pressing Ctrl+P on its own still prints the whole post. The one-page version only happens when you ask for it.

Clean up afterwards

The browser fires afterprint once the dialog closes, whether you printed or cancelled:

window.addEventListener('afterprint', () => {
  document.body.classList.remove('print-recipe-only');
  document.getElementById('print-sheet')?.remove();
});

Class off, sheet gone, page back to normal.

Why not visibility: hidden?

That was the first attempt, and it's the trick you'll find in a lot of answers online: set visibility: hidden on everything, then visibility: visible on the one element you want. It's attractive because visibility can be switched back on for a child even when the parent is hidden, which display can't.

The catch is in the name. Hidden isn't gone. An element with visibility: hidden still takes up exactly as much room as it did before. The header, the summary box, the short-version box, the prose, the related posts: all invisible, all still there, all still pushing the card down the page.

So the first printout was five pages, mostly blank, with the recipe floating somewhere in the middle. Technically correct. Completely useless in a kitchen.

Approach What the hidden stuff does Result
visibility: hidden on everything but the card Invisible, but keeps its space Blank pages around the recipe
display: none on the card's ancestors Removed, and takes the card with it Nothing prints
Clone to #print-sheet, display: none on the rest Removed from layout entirely Title, facts and card, starting at the top

Tip

When you want to print one piece of a page, move or copy it somewhere you can isolate it rather than hiding your way down to it. display: none really removes things. visibility: hidden only makes them invisible.

Is it always one page?

That's the goal, and the tight print type is sized for it. A really long recipe will still run to a second page; no stylesheet can fold paper. When it does, at least it's two pages of recipe.