What You'll Build

A QR Code Generator that creates QR codes for URLs, plain text, WiFi networks, email, phone numbers, and SMS. Users can download the QR as a PNG image. This project introduces you to using an external library — the standard practice in real-world development.

  • Six QR types: URL, Text, WiFi, Email, Phone, SMS
  • Tab switching between modes
  • Generate QR codes instantly as you type
  • Download the QR as a PNG file
  • Uses the qrcodejs library from a CDN
▶ Live Preview — this is what you'll build

What You'll Learn

Key new skill: Knowing when to use a library. QR encoding is a complex algorithm (Reed-Solomon error correction, matrix placement, masking). Writing it from scratch would take weeks. Using qrcodejs takes 3 lines. Real developers use libraries constantly.

1

Build the HTML with the CDN Script

The critical difference from previous projects: we load an external library. In the final index.html, you put it in the <head> or right before your script. This is a standard practice — CDNs host popular libraries so you don't have to.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>QR Code Generator</title>
</head>
<body>
    <div class="card">
        <h1>QR Code Generator</h1>
        <p class="sub">Pick a type, enter your data</p>

        <div class="tabs">
            <button class="tab active" data-type="url">URL</button>
            <button class="tab" data-type="text">Text</button>
            <button class="tab" data-type="wifi">WiFi</button>
            <button class="tab" data-type="email">Email</button>
            <button class="tab" data-type="phone">Phone</button>
            <button class="tab" data-type="sms">SMS</button>
        </div>

        <div class="panel active" id="panel-url">
            <div class="field">
                <label>Website URL</label>
                <input type="text" id="urlInput" value="https://makuistudio.com">
            </div>
        </div>

        <div class="panel" id="panel-text">
            <div class="field">
                <label>Plain Text</label>
                <textarea id="textInput">Hello from MAK Studio!</textarea>
            </div>
        </div>

        <div class="panel" id="panel-wifi">
            <div class="field">
                <label>Network Name (SSID)</label>
                <input type="text" id="wifiSsid" placeholder="MyWiFi">
            </div>
            <div class="row">
                <div class="field">
                    <label>Password</label>
                    <input type="text" id="wifiPass">
                </div>
                <div class="field">
                    <label>Security</label>
                    <select id="wifiSec">
                        <option value="WPA">WPA/WPA2</option>
                        <option value="WEP">WEP</option>
                        <option value="nopass">None</option>
                    </select>
                </div>
            </div>
        </div>

        <div class="panel" id="panel-email">
            <div class="field">
                <label>Email Address</label>
                <input type="email" id="emailTo" placeholder="hello@example.com">
            </div>
            <div class="field">
                <label>Subject (optional)</label>
                <input type="text" id="emailSubject">
            </div>
        </div>

        <div class="panel" id="panel-phone">
            <div class="field">
                <label>Phone Number</label>
                <input type="tel" id="phoneNum" placeholder="+1 555 123 4567">
            </div>
        </div>

        <div class="panel" id="panel-sms">
            <div class="field">
                <label>Phone Number</label>
                <input type="tel" id="smsNum" placeholder="+1 555 123 4567">
            </div>
            <div class="field">
                <label>Message (optional)</label>
                <textarea id="smsMsg"></textarea>
            </div>
        </div>

        <div class="qr-output" id="qrOutput">
            <div class="qr-placeholder">QR code will appear here</div>
        </div>

        <button class="download-btn" id="downloadBtn" disabled>Download PNG</button>
    </div>

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

About the CDN script: cdnjs.cloudflare.com is a free, reliable CDN that hosts thousands of popular JavaScript libraries. When your page loads, it fetches the library and makes it globally available as QRCode.

2

Style It with CSS

CSS
* { box-sizing: border-box; margin: 0; padding: 0; }

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: #f1f5f9;
    padding: 20px;
    color: #0f172a;
    min-height: 100vh;
}

.card {
    max-width: 460px;
    margin: 0 auto;
    background: #fff;
    border-radius: 16px;
    padding: 24px;
    box-shadow: 0 10px 40px rgba(15,23,42,0.08);
}

h1 { font-size: 1.3rem; margin-bottom: 4px; }
.sub { color: #64748b; font-size: 0.85rem; margin-bottom: 16px; }

.tabs {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 5px;
    margin-bottom: 16px;
}
.tab {
    background: #f1f5f9;
    border: 2px solid #e2e8f0;
    color: #64748b;
    padding: 8px 4px;
    border-radius: 8px;
    font-size: 0.75rem;
    font-weight: 700;
    cursor: pointer;
    transition: 0.2s;
    font-family: inherit;
}
.tab:hover { border-color: #cbd5e1; color: #334155; }
.tab.active {
    background: #eff6ff;
    border-color: #3b82f6;
    color: #1d4ed8;
}

.panel { display: none; margin-bottom: 12px; }
.panel.active { display: block; }

.field { margin-bottom: 10px; }
label {
    display: block;
    font-size: 0.75rem;
    font-weight: 600;
    color: #334155;
    margin-bottom: 4px;
}
input[type="text"],
input[type="email"],
input[type="tel"],
textarea,
select {
    width: 100%;
    padding: 10px 12px;
    border: 2px solid #e2e8f0;
    border-radius: 8px;
    font-size: 0.9rem;
    font-family: inherit;
    outline: none;
    transition: 0.2s;
}
input:focus, textarea:focus, select:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
}
textarea { min-height: 60px; resize: vertical; }

.row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

.qr-output {
    background: #f8fafc;
    border-radius: 12px;
    padding: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 240px;
    margin-bottom: 12px;
}
.qr-output canvas,
.qr-output img {
    max-width: 100%;
    height: auto;
    display: block;
    border-radius: 6px;
}
.qr-placeholder {
    color: #94a3b8;
    font-size: 0.9rem;
    text-align: center;
    font-style: italic;
}

.download-btn {
    width: 100%;
    padding: 12px;
    background: #3b82f6;
    color: #fff;
    border: none;
    border-radius: 10px;
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: 0.2s;
    font-family: inherit;
}
.download-btn:hover { background: #2563eb; }
.download-btn:disabled { background: #cbd5e1; cursor: not-allowed; }
3

Write the JavaScript — Setup & Payload Builder

The JavaScript has three main parts: building the payload string for each QR type, generating the QR code with the library, and handling the download.

JavaScript — Part 1
// State
var type = "url";
var tabs = document.querySelectorAll(".tab");
var qrOutput = document.getElementById("qrOutput");
var downloadBtn = document.getElementById("downloadBtn");
var lastCanvas = null;

// Tab switching
tabs.forEach(function(tab) {
    tab.addEventListener("click", function() {
        tabs.forEach(function(t) { t.classList.remove("active"); });
        tab.classList.add("active");
        type = tab.dataset.type;
        document.querySelectorAll(".panel").forEach(function(p) {
            p.classList.remove("active");
        });
        document.getElementById("panel-" + type).classList.add("active");
        generate();
    });
});

// ============================================================
// PAYLOAD BUILDER — the string that becomes the QR code
// ============================================================

function getPayload() {
    if (type === "url") {
        var url = document.getElementById("urlInput").value.trim();
        if (!url) return "";
        if (!/^https?:\/\//i.test(url)) url = "https://" + url;
        return url;
    }

    if (type === "text") {
        return document.getElementById("textInput").value;
    }

    if (type === "wifi") {
        var ssid = document.getElementById("wifiSsid").value.trim();
        var pass = document.getElementById("wifiPass").value;
        var sec = document.getElementById("wifiSec").value;
        if (!ssid) return "";
        var esc = function(s) {
            return s.replace(/([\\;,":])/g, "\\$1");
        };
        return "WIFI:T:" + sec + ";S:" + esc(ssid) + ";" +
               (sec !== "nopass" ? "P:" + esc(pass) + ";" : "") + ";";
    }

    if (type === "email") {
        var to = document.getElementById("emailTo").value.trim();
        var sub = document.getElementById("emailSubject").value.trim();
        if (!to) return "";
        return "mailto:" + to + (sub ? "?subject=" + encodeURIComponent(sub) : "");
    }

    if (type === "phone") {
        var num = document.getElementById("phoneNum").value.trim();
        if (!num) return "";
        return "tel:" + num;
    }

    if (type === "sms") {
        var sn = document.getElementById("smsNum").value.trim();
        var sm = document.getElementById("smsMsg").value.trim();
        if (!sn) return "";
        return "SMSTO:" + sn + (sm ? ":" + sm : "");
    }

    return "";
}

Why does each type have its own format? Phone cameras recognize standardized prefixes: WIFI:, mailto:, tel:, SMSTO:. When a phone scans a QR with one of these prefixes, it automatically opens the right app. This is a convention, not code — but every phone supports it.

4

Write the JavaScript — Generate & Download

JavaScript — Part 2
// ============================================================
// GENERATE — call the QRCode library
// ============================================================

function generate() {
    var payload = getPayload();
    qrOutput.innerHTML = "";

    if (!payload) {
        qrOutput.innerHTML = '
QR code will appear here
'; lastCanvas = null; downloadBtn.disabled = true; return; } if (typeof QRCode === "undefined") { qrOutput.innerHTML = '
Library failed to load.
'; return; } var container = document.createElement("div"); qrOutput.appendChild(container); // 3 lines to make a QR code — the library does the hard work new QRCode(container, { text: payload, width: 240, height: 240, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.M }); // Library draws asynchronously — wait, then grab the canvas setTimeout(function() { var canvas = container.querySelector("canvas"); var img = container.querySelector("img"); if (canvas) { lastCanvas = canvas; } else if (img) { // Some browsers use img fallback — convert to canvas var c = document.createElement("canvas"); c.width = 240; c.height = 240; var ctx = c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, 240, 240); ctx.drawImage(img, 0, 0, 240, 240); lastCanvas = c; } downloadBtn.disabled = !lastCanvas; }, 50); } // Live update on every input document.querySelectorAll("input, textarea, select").forEach(function(el) { el.addEventListener("input", generate); el.addEventListener("change", generate); }); // ============================================================ // DOWNLOAD — convert canvas to a PNG file // ============================================================ downloadBtn.addEventListener("click", function() { if (!lastCanvas) return; var link = document.createElement("a"); link.download = "qr-code.png"; link.href = lastCanvas.toDataURL("image/png"); link.click(); }); // Initial generation on page load generate();

Save all files and open index.html. The default URL generates a QR code. Try the other tabs — the QR updates when you type. Click Download PNG to save the image.

5

Understand the Big Ideas

1. Working with external libraries

The qrcodejs library provides a QRCode constructor. When you call new QRCode(element, options), it:

  1. Takes your text string
  2. Encodes it using the QR algorithm (Reed-Solomon error correction, matrix layout, masking)
  3. Draws the result as a canvas inside the element you provided

Writing that algorithm yourself would take weeks. Using the library takes 3 lines. This is the entire reason libraries exist — someone else already solved the hard problem, and they've shared their solution.

2. When to use a library vs. build it yourself

Use a library when: the problem is complex and well-solved (QR encoding, date formatting, charting, animations).
Build it yourself when: the problem is simple, you need to learn it, or the library is too heavy for what you need.

The BMI calculator had no dependencies — the math is simple. The QR generator uses a library because QR encoding is genuinely hard. Both decisions were correct.

3. Canvas to PNG download

The magic is canvas.toDataURL("image/png"). It reads the canvas pixels and returns a base64-encoded PNG data URI. Then:

JavaScript
var link = document.createElement("a");
link.download = "filename.png";
link.href = canvas.toDataURL("image/png");
link.click();  // triggers the browser's download

This same pattern downloads any canvas — screenshots, generated charts, drawings. Learn it once, use it everywhere.

4. Why setTimeout after generating?

The qrcodejs library renders the canvas asynchronously — it doesn't finish drawing the moment you call new QRCode(). So we wait 50ms with setTimeout before trying to grab the canvas for download.

Tip: 50ms is plenty for most devices. If you notice the download button not working reliably on slow phones, bump it to 100ms. It's a tiny delay users never notice.

6

Practice Challenges

🟢 Beginner

🟡 Intermediate

🔴 Advanced

Compare your solution: We built the production version with size options, error correction levels, colors, and more — see the live QR Code Generator.

Full Source Code

Common Mistakes & Fixes

❌ "QRCode is not defined"

The library didn't load. Check that the CDN script is present and there are no typos in the URL. Also check DevTools Network tab — the request to cdnjs.cloudflare.com should return a 200 status.

❌ "Nothing happens when I click Download"

The canvas isn't ready yet. Make sure you're using setTimeout to wait for the library to finish drawing. And verify lastCanvas is set before clicking download.

❌ "The QR is too small or too large"

Change the width and height in the new QRCode() options object. Both must be the same value — the code assumes a square.

❌ "WiFi QR doesn't connect"

Check the escaping. If the SSID or password has a semicolon or quote, it must be escaped with a backslash. The esc() function handles this.

❌ "QR doesn't scan when I add colors"

Keep dark-on-light for reliable scanning. Light-on-dark (inverted) QR codes often fail because most scanners expect the finder pattern to be dark. Never use light text on a dark background.

What to Build Next