Guide: Provide Your End Users with Access to a QR Code Generator

Hi All,

Demo here: QR Code generator

Note that the QR Code color, the QR code background-color, the QR code file type (image as Jpeg or PNG) and the QR code size are customizable.

It should also be responsive (:hot_face:).

Below is the full code. To be inserted in a custom code block

Made on top of qrcode.js library

Just Copy-Paste it

<html>
<head>  
    <style>
        :root {
            --container-bg-color: #ffffff;
            --container-border-radius: 8px;
            --box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);

            --title-font-size: 20px;
            --title-color: #333;

            --input-font-size: 16px;
            --input-border-color: #ccc;
            --input-focus-border-color: #007bff;
            --input-border-radius: 4px;

            --button-bg-color: #007BFF;
            --button-hover-bg-color: #0056b3;
            --button-text-color: #fff;
            --button-font-size: 16px;
            --button-border-radius: 6px;

            --label-font-size: 14px;
            --label-color: #333;
            --customization-label-font-size: 16px;
            --customization-label-color: #333;
            --color-group-gap: 16px;

            --display-qr-size: 150px;
            --download-qr-size: 300;

            --placeholder-color: #aaaaaa; 
            --placeholder-font-size: 15px; 
            --placeholder-font-weight: 400;
        }

        .qr-generator-wrapper {
            display: flex;
            justify-content: center;
            align-items: flex-start;
            padding: 20px;
            margin: 0 auto;
            max-width: 320px;
        }

        .qr-generator-container {
            text-align: left;
            padding: 20px;
            width: 100%;
            background-color: var(--container-bg-color);
            box-shadow: var(--box-shadow);
            border-radius: var(--container-border-radius);
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .qr-generator-container h1 {
            font-size: var(--title-font-size);
            margin-bottom: 20px;
            color: var(--title-color);
            order: -1;
        }

        .qr-code-section {
            width: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            order: 2;
        }

        .controls-section {
            width: 100%;
            order: 1;
        }

        .input-group {
            display: flex;
            gap: 8px;
            margin-bottom: 20px;
        }

        .input-group input[type="text"] {
            flex: 1;
            padding: 10px;
            font-size: var(--input-font-size);
            border: 1px solid var(--input-border-color);
            border-radius: var(--input-border-radius);
            outline: none;
            transition: border-color 0.2s ease;
        }

        .input-group input[type="text"]:focus {
            border-color: var(--input-focus-border-color);
        }

        .customization-label {
            font-size: var(--customization-label-font-size);
            color: var(--customization-label-color);
            margin-bottom: 10px;
            font-weight: 500;
            text-align: left;
        }

        .color-group {
            display: flex;
            flex-direction: column;
            gap: var(--color-group-gap);
            margin-bottom: 20px;
        }

        .color-group label {
            display: flex;
            flex-direction: column;
            font-size: var(--label-font-size);
            font-weight: 500;
            color: var(--label-color);
        }

        .color-group input[type="color"],
        .color-group input[type="number"],
        .color-group select {
            width: 100%;
            height: 40px;
            border: 1px solid var(--input-border-color);
            border-radius: var(--input-border-radius);
            padding: 8px;
            font-size: var(--input-font-size);
            outline: none;
            transition: border-color 0.2s ease;
        }

        #qrcode {
            margin-top: 20px;
            width: var(--display-qr-size);
            height: var(--display-qr-size);
            position: relative;
            display: inline-block;
        }

        .placeholder-text {
            color: var(--placeholder-color);
            font-size: var(--placeholder-font-size);
            font-weight: var(--placeholder-font-weight);
            margin-top: 20px;
            display: block; 
            text-align: center;
        }

        .message {
            margin-top: 10px;
            color: green;
            display: none;
            font-size: 14px;
            font-weight: 500;
            text-align: center;
        }

        .copy-button {
            background-color: #d3e8ff;
            border: 1px solid #d3e8ff;
            border-radius: 6px;
            margin-top: 10px;
            padding: 4px 8px;
            display: none;
            transition: background-color 0.4s ease;
        }
        
        .copy-button:hover {
             background-color: #007bff;
             color: #fff;
        }

        .button-container {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 10px; 
            margin-top: 20px;
            width: 100%;
        }

        .generate-button {
            background-color: var(--button-bg-color);
            color: var(--button-text-color);
            font-size: var(--button-font-size);
            border-radius: var(--button-border-radius);
            border: none;
            padding: 10px 20px;
            cursor: pointer;
            transition: all 0.3s ease;
            width: 100%;
        }

        .generate-button:disabled {
            background-color: #cccccc !important;
            cursor: not-allowed;
            opacity: 0.7;
        }

        .generate-button:not(:disabled):hover {
            background-color: var(--button-hover-bg-color);
        }

        .loading-spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid var(--button-bg-color);
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 0.8s linear infinite;
            display: none;
            margin-left: 10px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .download-button {
            background-color: var(--button-bg-color);
            color: var(--button-text-color);
            font-size: 14px;
            font-weight: 500;
            border-radius: var(--button-border-radius);
            border: none;
            padding: 6px 12px;
            cursor: pointer;
            transition: background-color 0.3s;
            margin-top: 1px;
            display: none;
            width: 100%;
        }

        .download-button:hover {
            background-color: var(--button-hover-bg-color);
        }

        /* Desktop styles */
        @media (min-width: 768px) {
            .qr-generator-wrapper {
                max-width: 600px;
            }

            .qr-generator-container {
                display: grid;
                grid-template-columns: auto 1fr;
            }

            .qr-code-section {
                width: var(--display-qr-size);
                order: 0;
            }

            .controls-section {
                order: 0;
            }

            .color-group {
                display: flex;
                flex-direction: row;
            }

            .generate-button,
            .download-button {
                width: auto;
            }

            .placeholder-text {
                margin-top: 80px;
            }
        }

        @media (min-width: 1024px) {
            .qr-generator-wrapper {
                max-width: 780px;
            }
        }
    </style>
</head>
<body>
<div class="qr-generator-wrapper">
    <div class="qr-generator-container">
        <div class="qr-code-section">
            <span class="placeholder-text" id="placeholder-text">The QR code will appear here.</span>
            <div id="qrcode"></div>
            <div class="button-container">
                <button id="download-button" class="download-button" onclick="downloadQRCode()">Instant Download</button>
            </div>
            <div class="message" id="copy-message">Downloaded!</div>
        </div>
        <div class="controls-section">
            <h1>QR Code Generator</h1>
            <div class="input-group">
                <input type="text" id="text-input" placeholder="Enter the desired URL" oninput="validateInput()">
            </div>
            <div class="customization-label">Customization:</div>
            <div class="color-group">
                <label>
                    QR Code Color
                    <input type="color" id="qr-color" value="#000000">
                </label>
                <label>
                    Background Color
                    <input type="color" id="bg-color" value="#ffffff">
                </label>
                <label>
                    QR Code Size (px)
                    <input type="number" id="qr-size" value="300" min="100" max="1600">
                </label>
                <label>
                    Download Format
                    <select id="qr-format">
                        <option value="jpeg">JPEG</option>
                        <option value="png">PNG</option>
                    </select>
                </label>
            </div>
            <button id="generate-button" class="generate-button" onclick="handleGenerateQRCode()" disabled>Generate</button>
            <div class="loading-spinner" id="loading-spinner"></div>
        </div>
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>

<script>
let isFirstGeneration = true;

function validateInput() {
    const input = document.getElementById('text-input');
    const generateButton = document.getElementById('generate-button');
    generateButton.disabled = !input.value.trim();
}

document.addEventListener('DOMContentLoaded', validateInput);

function handleGenerateQRCode() {
    const generateButton = document.getElementById("generate-button");
    const loadingSpinner = document.getElementById("loading-spinner");
    const downloadButton = document.getElementById("download-button");
    
    generateButton.disabled = true;
    loadingSpinner.style.display = "inline-block";

    if (isFirstGeneration) {
        generateButton.textContent = "Re-Generate";
        isFirstGeneration = false;
    }

    setTimeout(() => {
        generateQRCode();
        validateInput(); // Re-validate input after generation
        loadingSpinner.style.display = "none";
        downloadButton.style.display = "block";
    }, 1000);
}

function generateQRCode() {
    const input = document.getElementById("text-input").value;
    const qrCodeContainer = document.getElementById("qrcode");
    const qrColor = document.getElementById("qr-color").value;
    const bgColor = document.getElementById("bg-color").value;

    // Clear previous QR code
    qrCodeContainer.innerHTML = "";

    const qrcode = new QRCode(qrCodeContainer, {
        text: input,
        width: 150,
        height: 150,
        colorDark: qrColor,
        colorLight: bgColor,
    });

    const placeholderText = document.getElementById("placeholder-text");
    placeholderText.style.display = "none";
}

function downloadQRCode() {
    const qrCodeContainer = document.getElementById("qrcode");
    const qrCodeCanvas = qrCodeContainer.querySelector("canvas");
    const qrSize = parseInt(document.getElementById("qr-size").value) || 300;
    const format = document.getElementById("qr-format").value;

    if (!qrCodeCanvas) {
        alert("Please generate a QR code before downloading.");
        return;
    }

    const downloadQRCodeContainer = document.createElement("div");
    const downloadQRCode = new QRCode(downloadQRCodeContainer, {
        text: document.getElementById("text-input").value,
        width: qrSize,
        height: qrSize,
        colorDark: document.getElementById("qr-color").value,
        colorLight: document.getElementById("bg-color").value,
    });

    const downloadCanvas = downloadQRCodeContainer.querySelector("canvas");

    if (downloadCanvas) {
        const tempCanvas = document.createElement("canvas");
        const ctx = tempCanvas.getContext("2d");
        tempCanvas.width = qrSize;
        tempCanvas.height = qrSize;

        // Fill background (important for JPEG format)
        ctx.fillStyle = document.getElementById("bg-color").value;
        ctx.fillRect(0, 0, qrSize, qrSize);
        
        ctx.drawImage(downloadCanvas, 0, 0, qrSize, qrSize);

        const link = document.createElement("a");
        
        const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png';
        const fileExtension = format === 'jpeg' ? 'jpg' : 'png';
        
        const dataURL = format === 'jpeg' 
            ? tempCanvas.toDataURL(mimeType, 0.9) 
            : tempCanvas.toDataURL(mimeType);

        link.href = dataURL;
        link.download = `qrcode.${fileExtension}`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        tempCanvas.remove();
        downloadQRCodeContainer.remove();
    }

    const copyMessage = document.getElementById("copy-message");
    copyMessage.style.display = "block";
    setTimeout(() => {
        copyMessage.style.display = "none";
    }, 3000);
}

</script>
</body>
</html>
3 Likes

Love it Matthieu!
I would like to use it in a project, but 2 questions:

  1. I want to input the URL from my Airtable database, how do I do that?
  2. I want to save the generated QR code in my database

Complex to do it, it can’t be done like this without the user to make an additional action.

Your use case intends to create a pre-made QR code, which is not the case of this QR code generator (more related to a free use from the end users)

Are you sure this wouldn’t be more useful for you?
Refer to the QR code part here :point_down: