DEV Community

Cover image for Printing HTML best technique with sample receipt.
Alish Giri
Alish Giri

Posted on

Printing HTML best technique with sample receipt.

I have been experementing with printing invoices using Javascript and Tailwindcss. After several trials and errors the following is the best configuration I found to get the optimal results.

(optional) Configure Tailwindcss

If you are using tailwindcss to style your invoice then you can set the following configuration to access to 'print' and 'screen' prefixes that you can use to hide/show content based on your requirements.

/** @type {import('tailwindcss').Config} */
export default {
    content: ['./src/**/*.{html,js,svelte,ts}'],
    theme: {
        extend: {
            screens: {
                print: { raw: 'print' },
                screen: { raw: 'screen' }
            },
            // ...
        }
    },
    plugins: []
};
Enter fullscreen mode Exit fullscreen mode

Now you can utilize this as follows:

<div class="screen:bg-red-300 print:bg-white" />
Enter fullscreen mode Exit fullscreen mode

Add this to your primary CSS file

/* This will hide the extra header and footer contents added by the browser. */
@media print {
    @page {
        margin: 0.3in 0.7in 0.3in 0.7in !important;
    }
}

/* Change this as you like */
@media screen {
    html,
    body {
        width: 100vw;
        height: 100vh;
        display: flex;
        overflow: auto;
        background-color: #982b44;
    }
}
Enter fullscreen mode Exit fullscreen mode

Always use a separate route, do not use a pop up window.

Also, set the document title for better experience.

<head>
    ...
    <title>your-file-name</title>
    ...
</head>
Enter fullscreen mode Exit fullscreen mode

or

document.title = "your-file-name"
Enter fullscreen mode Exit fullscreen mode

Use join('') to hide the unnecessary commas, if you are looping through the items like below.

const tableRows = orders.map((item, index) => {
    // ...
    return `
    <tr class="border-b border-gray-200 break-inside-avoid break-before-auto">
        <td class="max-w-0 py-2 pl-4 pr-3 text-sm sm:pl-0">
            <div class="font-medium text-gray-900"><span class="mr-4 text-gray-500">${index + 1}.</span>${item.name}</div>
        </td>
        <td class="hidden px-3 py-2 text-right text-sm text-gray-500 sm:table-cell">${quantity}${weightUnit}</td>
        <td class="hidden px-3 py-2 text-right text-sm text-gray-500 sm:table-cell">${price}</td>
        <td class="py-2 pl-3 pr-4 text-right text-sm text-gray-500 sm:pr-0"><span class="text-xs">${item.currency}</span> ${total}</td>
    </tr>
    `;
});
Enter fullscreen mode Exit fullscreen mode

and display this as,

<tbody>
    ${tableRows.join('')}
</tbody>
Enter fullscreen mode Exit fullscreen mode

Sample Receipt


export function receiptGenerator(seller: any, order: any): string {
    const panNum = 'XXXXXXXX';
    const companyLogo = // your-company-logo
    const deliveryAddr = order.deliveryAddress;

    let vat = 0.0;
    let subTotal = 0;
    let currency = '';
    const tableRows = order.items.map((item, index) => {
        // ...
        return `
            <tr class="border-b border-gray-200 break-inside-avoid break-before-auto">
                <td class="max-w-0 py-2 pl-4 pr-3 text-sm sm:pl-0">
                    <div class="font-medium text-gray-900"><span class="mr-4 text-gray-500">${index + 1}.</span>${item.name}</div>
                </td>
                <td class="hidden px-3 py-2 text-right text-sm text-gray-500 sm:table-cell">${quantity}${weightUnit}</td>
                <td class="hidden px-3 py-2 text-right text-sm text-gray-500 sm:table-cell">${price}</td>
                <td class="py-2 pl-3 pr-4 text-right text-sm text-gray-500 sm:pr-0"><span class="text-xs">${item.currency}</span> ${total}</td>
            </tr>
        `;
    });
    return `
        <div class="mx-auto my-6 max-w-max rounded bg-white p-6 shadow-sm text-black">
            <div class="grid grid-cols-2 items-center">
                <div>
                <img width="100" height="100" alt="company-logo" src="${companyLogo}" />
                </div>

                <div class="text-right">
                <p>${seller.name}</p>
                <p class="text-sm text-gray-500">${seller.email}</p>
                <p class="mt-1 text-sm text-gray-500">${seller.phoneNumber}</p>
                <p class="mt-1 text-sm text-gray-500">VAT: ${panNum}</p>
                </div>
            </div>

            <!-- Client info -->
            <div class="mt-8 grid grid-cols-2 items-center">
                <div>
                <p class="font-bold text-gray-800">Bill to :</p>
                <p class="text-gray-500">
                    ${deliveryAddr.addressLine1}
                    <br />
                    ${deliveryAddr.state}, ${deliveryAddr.city} ${deliveryAddr.postcode}, ${deliveryAddr.country}
                </p>
                <p class="text-gray-500">info@email.com</p>
                </div>

                <div class="text-right">
                <p class="">
                    Invoice number:
                    <span class="text-gray-500">${displayInvoiceNumber(order.orderNumber)}</span>
                </p>
                <p class="text-sm">
                    Invoice Date: <span class="text-gray-500">${dateTime.objectIdToDate(order.id)}</span>
                    <br />
                    Due Date: <span class="text-gray-500">${dateTime.objectIdToDate(order.id)}</span>
                </p>
                </div>
            </div>

            <!-- Invoice Items -->
            <div class="-mx-4 mt-8 flow-root sm:mx-0">
                <table class="min-w-full break-inside-auto">
                    <colgroup class="break-inside-auto">
                        <col class="w-full sm:w-1/2" />
                        <col class="sm:w-auto" />
                        <col class="sm:w-auto" />
                        <col class="sm:w-1/5" />
                    </colgroup>
                    <thead class="border-b border-gray-300 text-gray-900 table-header-group">
                        <tr>
                            <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Items</th>
                            <th scope="col" class="hidden px-3 py-3.5 text-right text-sm font-semibold text-gray-900 sm:table-cell">Quantity</th>
                            <th scope="col" class="hidden px-3 py-3.5 text-right text-sm font-semibold text-gray-900 sm:table-cell">Price</th>
                            <th scope="col" class="py-3.5 pl-3 pr-4 text-right text-sm font-semibold text-gray-900 sm:pr-0">Amount</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${tableRows.join('')}
                    </tbody>
                </table>
                <table class="min-w-full">
                    <colgroup>
                        <col class="w-full sm:w-1/2" />
                        <col class="sm:w-auto" />
                        <col class="sm:w-auto" />
                        <col class="sm:w-1/5" />
                    </colgroup>
                    <thead>
                        <tr>
                            <th scope="row" colspan="3" class="hidden pl-4 pr-3 pt-6 text-right text-sm font-normal text-gray-500 sm:table-cell sm:pl-0">Subtotal</th>
                            <th scope="row" class="pl-6 pr-3 pt-2 text-left text-sm font-normal text-gray-500 sm:hidden">Subtotal</th>
                            <td class="pl-3 pr-6 pt-2 text-right text-sm text-gray-500 sm:pr-0"><span class="text-xs">${currency}</span> ${Math.round(subTotal) / 100}</td>
                        </tr>
                        <tr>
                            <th scope="row" colspan="3" class="hidden pl-4 pr-3 pt-2 text-right text-sm font-normal text-gray-500 sm:table-cell sm:pl-0">Tax</th>
                            <th scope="row" class="pl-6 pr-3 pt-2 text-left text-sm font-normal text-gray-500 sm:hidden">Tax</th>
                            <td class="pl-3 pr-6 pt-2 text-right text-sm text-gray-500 sm:pr-0"><span class="text-xs">${currency}</span> ${Math.round(subTotal * vat * 100) / 100}</td>
                        </tr>
                        <tr>
                            <th scope="row" colspan="3" class="hidden pl-4 pr-3 pt-2 text-right text-sm font-normal text-gray-500 sm:table-cell sm:pl-0">Discount</th>
                            <th scope="row" class="pl-6 pr-3 pt-2 text-left text-sm font-normal text-gray-500 sm:hidden">Discount</th>
                            <td class="pl-3 pr-6 pt-2 text-right text-sm text-gray-500 sm:pr-0">-</td>
                        </tr>
                        <tr>
                            <th scope="row" colspan="3" class="hidden pl-4 pr-3 pt-2 text-right text-sm font-semibold text-gray-900 sm:table-cell sm:pl-0">Total</th>
                            <th scope="row" class="pl-6 pr-3 pt-2 text-left text-sm font-semibold text-gray-900 sm:hidden">Total</th>
                            <td class="pl-3  pt-2 text-right text-sm font-semibold text-gray-900 sm:pr-0"><span class="text-xs">${currency}</span> ${Math.round((subTotal + subTotal * vat) * 100) / 100}</td>
                        </tr>
                    </thead>
                </table>
            </div>

            <!--  Footer  -->
            <div class="mt-16 border-t-2 pt-4 text-center text-xs text-gray-500">Please pay the invoice before the due date. You can pay the invoice by logging in to your account from our client portal.</div>
        </div>
    `;
}
Enter fullscreen mode Exit fullscreen mode

Hope this helps! Took me two days to make this perfect!

Top comments (0)