Build a Pro QR Code Generator 📱
QR Codes are everywhere. Menus, Wi-Fi logins, business cards. Building a generator is a fantastic project because it combines String Formatting (structuring the data) with Canvas Manipulation (drawing the image).
In this specific guide, we will build a versatile generator that supports:
- URLs (Standard links)
- Wi-Fi Access (Auto-join networks)
- vCards (Contact sharing)
- Custom Colors
Step 1: The Library (Don't Reinvent the Wheel) 📦
Generating the matrix of black and white squares requires complex Reed-Solomon error correction math.
Don't write this from scratch. Use the battle-tested qrcode library.
npm install qrcode
npm install @types/qrcode --save-dev
Step 2: The Data Formats (The Secret Sauce) 🤫
A QR code is just text. The Phone Camera decides what to do based on the format of that text.
1. URL:
Just the link.
https://google.com
2. Wi-Fi Network:
Special format string.
WIFI:S:MyNetworkName;T:WPA;P:MyPassword;;
S: SSID (Name)T: Type (WPA/WEP)P: Password
3. vCard (Contact): Standard VCF structure.
BEGIN:VCARD
VERSION:3.0
FN:John Doe
TEL:555-123-4567
EMAIL:john@example.com
END:VCARD
Step 3: Generating the Image 🖼️
The qrcode library has a handy toDataURL method. It returns a Base64 string (data:image/png;base64...) that you can put directly into an <img> tag.
import QRCode from 'qrcode'
const generate = async (text) => {
try {
const url = await QRCode.toDataURL(text, {
width: 400,
margin: 2,
color: {
dark: '#000000', // Foreground
light: '#ffffff' // Background
}
})
setQrImage(url)
} catch (err) {
console.error(err)
}
}
Step 4: The Full React Component 💻
Here is a simplified version of the powerful generator used on FastTools. It features a Tabbed interface to switch between URL, Wi-Fi, and Text modes.
"use client"
import { useState, useEffect } from "react"
import QRCode from "qrcode"
import { Download, Wifi, Link, Type } from "lucide-react"
export default function QRCodeGenerator() {
const [activeTab, setActiveTab] = useState("url") // 'url' | 'wifi' | 'text'
const [qrImage, setQrImage] = useState("")
// Inputs
const [url, setUrl] = useState("https://example.com")
const [text, setText] = useState("")
const [wifiSsid, setWifiSsid] = useState("")
const [wifiPass, setWifiPass] = useState("")
// --- LOGIC ---
// 1. Determine the raw string based on active tab
const getQRData = () => {
if (activeTab === "url") return url
if (activeTab === "text") return text
if (activeTab === "wifi") {
// Special Wi-Fi Formatting
return `WIFI:S:${wifiSsid};T:WPA;P:${wifiPass};;`
}
return ""
}
// 2. Generate the visual QR Code
const generateQR = async () => {
const data = getQRData()
if (!data) return
try {
// Generate Data URL
const dataUrl = await QRCode.toDataURL(data, {
width: 600,
margin: 2,
color: {
dark: "#000000",
light: "#ffffff",
},
})
setQrImage(dataUrl)
} catch (err) {
console.error(err)
}
}
// Auto-generate when inputs change (Debounced in real apps)
useEffect(() => {
generateQR()
}, [url, text, wifiSsid, wifiPass, activeTab])
// 3. Download Logic
const downloadQR = () => {
const link = document.createElement("a")
link.download = "qrcode.png"
link.href = qrImage
link.click()
}
return (
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto p-6">
{/* LEFT COL: Inputs */}
<div className="space-y-6">
<h2 className="text-2xl font-bold">QR Generator</h2>
{/* Tabs */}
<div className="flex gap-2 p-1 bg-slate-100 rounded-lg">
{['url', 'wifi', 'text'].map(type => (
<button
key={type}
onClick={() => setActiveTab(type)}
className={`flex-1 py-2 rounded-md text-sm font-medium capitalize transition ${
activeTab === type ? 'bg-white shadow text-blue-600' : 'text-slate-500 hover:text-slate-700'
}`}
>
{type}
</button>
))}
</div>
{/* Input Fields (Conditional) */}
<div className="bg-white border rounded-xl p-6 shadow-sm min-h-[200px]">
{activeTab === 'url' && (
<div className="space-y-2">
<label className="text-sm font-bold text-slate-700">Website URL</label>
<input
className="w-full p-3 border rounded focus:ring-2 focus:ring-blue-500 outline-none"
value={url}
onChange={e => setUrl(e.target.value)}
placeholder="https://..."
/>
</div>
)}
{activeTab === 'wifi' && (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-bold text-slate-700">Network Name (SSID)</label>
<input
className="w-full p-3 border rounded focus:ring-2 focus:ring-blue-500 outline-none"
value={wifiSsid}
onChange={e => setWifiSsid(e.target.value)}
placeholder="MyHomeWiFi"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-slate-700">Password</label>
<input
className="w-full p-3 border rounded focus:ring-2 focus:ring-blue-500 outline-none"
type="password"
value={wifiPass}
onChange={e => setWifiPass(e.target.value)}
placeholder="SecretKey123"
/>
</div>
</div>
)}
{activeTab === 'text' && (
<div className="space-y-2">
<label className="text-sm font-bold text-slate-700">Plain Text</label>
<textarea
className="w-full p-3 border rounded focus:ring-2 focus:ring-blue-500 outline-none h-32"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Enter message..."
/>
</div>
)}
</div>
</div>
{/* RIGHT COL: Preview */}
<div className="flex flex-col items-center justify-center space-y-6">
<div className="relative group">
{/* The QR Image */}
{qrImage ? (
<img
src={qrImage}
alt="Generated QR Code"
className="w-64 h-64 rounded-xl shadow-lg border-4 border-white"
/>
) : (
<div className="w-64 h-64 bg-slate-100 rounded-xl flex items-center justify-center text-slate-400">
Generating...
</div>
)}
</div>
<button
onClick={downloadQR}
className="flex items-center gap-2 px-6 py-3 bg-slate-900 text-white font-bold rounded-full hover:bg-slate-800 transition shadow-lg hover:shadow-xl transform hover:-translate-y-1"
>
<Download size={18} /> Download PNG
</button>
</div>
</div>
)
}
Step 5: Advanced Tips (Customization) 🎨
The qrcode library is powerful. You can customize the color object to match your brand.
color: {
dark: '#2563EB', // Blue dots
light: '#00000000' // Transparent background! (Use 8-digit hex)
}
Note: Be careful with colors. If the contrast is too low (e.g., yellow dots on white background), phone cameras won't scan it. Always stick to Dark on Light for maximum compatibility.
Step 6: Why Build It Client-Side? 🚀
Many "free" QR generators require a server. They often:
- Track your data.
- Put the link behind a redirect (so it breaks if their site goes down).
- Display ads.
By building it entirely in React with useEffect and canvas, the QR code is generated instantly on the user's device. No data leaves the browser, and the code works forever. That's real value.