controlling print styles with Alpine.js
Plain @media print CSS reliably hides navigation and buttons when printing, but it hits a hard limit as soon as content has already been removed from rendering by Alpine's x-show with an inline display: none. A small Alpine state that deliberately shifts every relevant area into a printable state before printing starts closes exactly that gap.
Table of Contents
- 1. The limit of pure @media print CSS
- 2. Why JavaScript becomes unavoidable at this point
- 3. A central Alpine store for print state
- 4. Practical example: an invoice view without navigation and without buttons
- 5. Reliably using the beforeprint and afterprint events
- 6. Print utilities with Tailwind: print:hidden and print:block
- 7. A note for the Hyvä context: store registration and CSP
- 8. Reliably testing print styles
- 9. A checklist for print-friendly Alpine views
- 10. Summary
- 11. FAQ
1. The limit of pure @media print CSS
A simple rule like @media print { .no-print { display: none } } reliably excludes static elements that always exist in the DOM, such as a fixed navigation bar or a button area, from the printout. That works because the browser applies the normal CSS cascade rules when printing, and @media print is simply an additional print-specific context for that cascade.
That rule fails, though, for content Alpine has already given an inline display: none via x-show at runtime, say a collapsed accordion or a detail panel hidden by default. An inline style carries higher specificity in the CSS cascade than almost any regular CSS rule, including an @media print rule, which means the element simply stays invisible when printing, no matter how the print stylesheet rules are written.
2. Why JavaScript becomes unavoidable at this point
Making an element hidden by an inline style visible again for print takes more than extra CSS specificity, short of reaching for !important, which in turn interferes deeply with the rest of the cascade and becomes hard to maintain. The more reliable path is to change the underlying Alpine state itself before printing, so Alpine removes the inline style on its own instead of trying to override it from the outside with CSS.
That shifts the task from pure CSS to a small, coordinated state: a printMode flag in an Alpine store, set right before the actual print action, can specifically extend every affected x-show condition so it evaluates to true in print mode, regardless of the actual interactive state.
3. A central Alpine store for print state
Instead of maintaining its own printMode value in every single component, a global Alpine.store('print', ...) bundles the state in one place any component on the page can access. That matters especially because an invoice view typically consists of several independent Alpine components, say a line-item table, a payment status panel, and a footer, that all need to react to the same print state at the same time.
The store itself stays deliberately minimal: a boolean flag and two methods, one to activate print mode followed by a window.print() call, and a second that uses the browser's afterprint event to automatically restore the original interactive state once the print dialog closes, regardless of whether anything was actually printed or the dialog was cancelled.
document.addEventListener('alpine:init', () => {
Alpine.store('print', {
active: false,
start() {
this.active = true;
window.requestAnimationFrame(() => window.print());
},
});
});
window.addEventListener('afterprint', () => {
Alpine.store('print').active = false;
});
4. Practical example: an invoice view without navigation and without buttons
A typical invoice view in a customer account has navigation, a 'Save as PDF' button, and several detail sections collapsed by default, say an expandable breakdown of tax rates per line item. For printing, navigation and buttons should disappear, while the collapsed tax rate details, normally only visible on click, should appear in full on the printout.
Navigation and buttons can still be hidden with plain CSS via a print:hidden utility class, since they sit statically in the DOM and are never hidden by an inline style. The collapsed detail sections, on the other hand, need the store's active condition added alongside the actual click logic, so they become visible in print mode regardless of the interactive state.
<div x-data="{ open: false }">
<button x-on:click="open = !open" class="print:hidden text-sm underline">
Show tax rates
</button>
<div x-show="open || $store.print.active" class="mt-2 text-sm text-gray-600">
<p>19% VAT: 45.60 EUR</p>
<p>7% VAT: 3.20 EUR</p>
</div>
</div>
<nav class="print:hidden">...</nav>
<button x-on:click="$store.print.start()" class="print:hidden">
Print
</button>
5. Reliably using the beforeprint and afterprint events
Besides programmatically setting printMode before window.print(), it's also worth adding a global listener on the native beforeprint event, which the browser fires even when the user opens the print dialog not through the custom button but via the browser's keyboard shortcut or menu. Without that extra listener, print mode would not activate in that case, and the collapsed sections would stay invisible despite the Alpine logic.
The afterprint event reliably handles the reset, regardless of whether the user actually printed or cancelled the dialog, since the browser fires this event in both cases. That keeps the page's interactive state consistent after the print dialog closes with the state before it opened, with no extra logic needed for the cancellation case.
window.addEventListener('beforeprint', () => {
Alpine.store('print').active = true;
});
window.addEventListener('afterprint', () => {
Alpine.store('print').active = false;
});
6. Print utilities with Tailwind: print:hidden and print:block
Tailwind ships its own variant for @media print via the print: prefix, so classes like print:hidden or print:block can be written directly in the template with no separate CSS to maintain. For every element that is not toggled by an Alpine inline style but only statically depending on context, this utility class is entirely sufficient and makes the store detour from the previous sections unnecessary.
The store approach is only needed for the cases where Alpine already sets an inline style through x-show that a plain CSS rule like print:block cannot override. A clear rule of thumb helps with the decision: if an element is only shown or hidden through classes, Tailwind's print variant is enough, if an element is controlled by x-show or x-if, it needs the store condition.
7. A note for the Hyvä context: store registration and CSP
In a Hyvä theme, the global print store is typically registered centrally in a single, reusable inline script block, included on every page with printable content, say through a dedicated layout handle for invoice and order views. Like any other Alpine inline block, this one also needs to be registered through the theme's CSP component so the strict Content Security Policy doesn't block it.
Since beforeprint and afterprint are global window events, registering a single central listener for the entire page is enough, regardless of how many individual Alpine components access the print store. That avoids duplicate listeners and keeps the logic in one single, easy-to-find spot in the theme.
8. Reliably testing print styles
The browser's print preview, usually reachable via the keyboard shortcut for the print dialog, is enough for most testing and already shows whether the print:hidden classes and the dynamically revealed detail sections work together correctly. For automated tests, it also works to trigger the beforeprint and afterprint events directly via JavaScript in a headless browser, without actually needing to open a print dialog.
A commonly overlooked test case is cancelling the print dialog without actually printing, say by clicking 'Cancel'. Since afterprint fires in that case too, it should be explicitly verified that the interactive state resets correctly afterward and no detail sections accidentally remain permanently stuck in the expanded state.
9. A checklist for print-friendly Alpine views
A robust solution meets five points: static elements are hidden via Tailwind's print utilities, elements hidden by Alpine are additionally made visible in print mode through a store condition, both the custom print button and the native beforeprint event activate print mode, afterprint resets the state reliably, and the print dialog's cancellation path has been explicitly tested.
Anyone implementing all five points gets a print-friendly view that works regardless of how the user opens the print dialog, and that returns to exactly the state the page was in before printing once the dialog closes.
| Situation | Is CSS alone enough? | Recommended approach | Reason |
|---|---|---|---|
| Hiding static navigation | Yes | print:hidden class |
Element always sits in the DOM, no inline style |
| Printing a panel hidden via x-show | No | Add an Alpine store condition | Inline style has higher specificity than @media print |
| Triggering print via a custom button | No | window.print() after a store update | State must be set active before printing |
| Triggering print via a browser shortcut | No | beforeprint/afterprint listener | The custom button handler is never triggered in that case |
Mironsoft
Alpine.js interactivity for Hyvä frontends
A Hyvä frontend that needs more interactivity, but without React overhead?
We build interactive frontend components for Hyvä themes with Alpine.js, lightweight and without build-step complexity, from simple toggles to complex form flows.
Custom Components
Develop interactive Alpine.js components for specific shop requirements.
Performance Review
Review existing Alpine.js implementations for reactivity pitfalls and performance.
Team Training
Bring developers up to speed on Alpine.js patterns for Hyvä themes hands-on.
10. Summary
Print styles with Alpine.js
Core idea
Plain @media print CSS cannot re-reveal elements Alpine has hidden with an inline style, a store flag can.
Practical benefit
A central Alpine.store('print') coordinates print state across several independent components.
Biggest pitfall
Printing via a browser keyboard shortcut never triggers a custom button handler, only a beforeprint listener reliably catches that case.
Recommendation
Hide static elements via Tailwind's print utility, reveal dynamically hidden elements via a store condition in print mode.