qrlayout-ui
v1.1.0
Published
Embeddable drag-and-drop QR label designer — works in React, Vue, Svelte, Angular and vanilla JS. Pairs with qrlayout-core.
Maintainers
Readme
qrlayout-ui
An embeddable, framework-agnostic drag-and-drop QR label designer for React, Angular, Vue, Svelte, and plain HTML.
Drop a professional label designer into your app in minutes. Supports drag-and-drop element placement, live preview, dark mode, data binding with {{variables}}, and layout export to JSON.
Part of the QR Layout Tool monorepo.
🚀 Live Demos & Examples
Try the designer — no signup needed:
| Framework | Live Demo | Source Code | | :--- | :--- | :--- | | React | ▶ Open Demo | Source | | Angular | ▶ Open Demo | Source | | Svelte 5 | ▶ Open Demo | Source | | Vue 3 | ▶ Open Demo | Source |

✨ Features
- Framework Independent: Built with vanilla TypeScript — mount it inside React, Vue, Angular, Svelte, or a plain HTML page.
- Drag & Drop Designer: Visual placement and resizing of text and QR code elements on a canvas.
- Live Preview: Preview your label with real sample data as you design.
- Data Binding: Bind any field like
{{name}},{{id}}, or{{department}}from your entity schema. - Multi-Variable QR: Set a separator (e.g.
|) on QR elements to automatically join multiple fields into one scan. - Rich Text Styling: Customize font family, size, weight, color, and both horizontal and vertical alignment.
- Dark Mode: Built-in light and dark themes that follow your app's color scheme.
- Flexible Units: Design in millimeters, centimeters, inches, or pixels.
- JSON Output: Save the layout as a compact JSON object to your backend or
localStorage. - Rendering via
qrlayout-core: The same layout JSON drives PNG, PDF, and ZPL export — design once, output anywhere.
📦 Installation
npm install qrlayout-ui qrlayout-core🚀 Getting Started
Step 1 — Import the CSS
Add this to your project's entry point (e.g., main.ts, index.js, App.tsx):
import "qrlayout-ui/style.css";Step 2 — Mount the Designer
import { QRLayoutDesigner } from "qrlayout-ui";
const designer = new QRLayoutDesigner({
element: document.getElementById("designer-container"),
// Optional: entity schema for {{variable}} data binding
entitySchemas: {
employee: {
label: "Employee",
fields: [
{ name: "name", label: "Full Name" },
{ name: "id", label: "Employee ID" },
{ name: "department", label: "Department" },
],
sampleData: {
name: "Alice Johnson",
id: "EMP-001",
department: "Engineering"
}
}
},
// Optional: load an existing saved layout
initialLayout: {
id: "1",
name: "My Badge",
targetEntity: "employee",
width: 100,
height: 60,
unit: "mm",
backgroundColor: "#ffffff",
elements: []
},
// Fires when the user clicks "Save Layout"
onSave: (layout) => {
localStorage.setItem("my-layout", JSON.stringify(layout));
console.log("Layout saved:", layout);
}
});Step 3 — Size the Container
The designer fills 100% of its parent element. Give the container a fixed size:
#designer-container {
width: 100%;
height: 100vh; /* full-screen */
}Or embed it inside a modal/panel:
#designer-container {
width: 100%;
height: 700px;
}Step 4 — Cleanup on Unmount
designer.destroy();⚙️ Options Reference
| Option | Type | Required | Description |
|---|---|---|---|
| element | HTMLElement | ✅ | The DOM element to mount the designer into |
| entitySchemas | Record<string, Schema> | ❌ | Entity field definitions for {{field}} binding and live preview |
| initialLayout | StickerLayout | ❌ | Layout to pre-load on mount |
| onSave | (layout: StickerLayout) => void | ❌ | Callback when "Save Layout" is clicked |
💾 Layout Persistence & Printing Workflow
The onSave callback gives you the complete layout as a plain JSON object. That JSON is your single source of truth — store it anywhere, load it back anytime, and pass it with real data to qrlayout-core for printing.
The Full Cycle
┌──────────────────────┐
│ qrlayout-ui │ ← User designs the label visually
│ (Designer) │
└──────────┬───────────┘
│ onSave(layout JSON)
▼
┌──────────────────────┐
│ Your Backend / │ ← Save layout JSON to your DB, file, or localStorage
│ Database │
└──────────┬───────────┘
│ Load layout JSON + real employee/machine data
▼
┌──────────────────────┐
│ qrlayout-core │ ← Merge data into template, export PNG / PDF / ZPL
│ (StickerPrinter) │
└──────────────────────┘Step 1 — Save the Layout to Your Database
const designer = new QRLayoutDesigner({
element: document.getElementById("designer"),
onSave: async (layout) => {
// layout is a plain JSON object — save it wherever you like
await fetch("/api/layouts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(layout)
});
}
});Step 2 — Load the Layout Back (with initialLayout)
// Fetch a saved layout from your backend
const saved = await fetch("/api/layouts/employee-badge").then(r => r.json());
const designer = new QRLayoutDesigner({
element: document.getElementById("designer"),
initialLayout: saved, // ← the JSON you saved earlier
onSave: (layout) => { /* update in DB */ }
});Step 3 — Print with Real Data (via qrlayout-core)
import { StickerPrinter } from "qrlayout-core";
const printer = new StickerPrinter();
// Fetch layout + real records from your backend
const layout = await fetch("/api/layouts/employee-badge").then(r => r.json());
const records = await fetch("/api/employees").then(r => r.json());
// Batch export — one PNG/PDF/ZPL per record
const zplPages = printer.exportToZPL(layout, records);
// or export as PDF
const pdf = await printer.exportToPDF(layout, records);
pdf.save("batch-badges.pdf");[!NOTE] About the live demo applications — The demo apps (React, Angular, Svelte, Vue) ship with a small set of pre-built sample layouts and employee/machine records so you can explore all features immediately without any setup.
All demo data is stored exclusively in your browser's
localStorage. Nothing is sent to or stored on any server. Clearing your browser storage resets the demo to its defaults. Your designs and data never leave your browser.
🔌 Integration Examples
React (TypeScript)
import { useEffect, useRef } from 'react';
import { QRLayoutDesigner } from 'qrlayout-ui';
import 'qrlayout-ui/style.css';
const LabelDesigner = () => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const designer = new QRLayoutDesigner({
element: containerRef.current,
onSave: (layout) => console.log('Saved:', layout)
});
return () => designer.destroy();
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />;
};
export default LabelDesigner;Vue 3 (Composition API)
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { QRLayoutDesigner } from 'qrlayout-ui';
import 'qrlayout-ui/style.css';
const container = ref(null);
let designer = null;
onMounted(() => {
designer = new QRLayoutDesigner({
element: container.value,
onSave: (layout) => console.log('Saved:', layout)
});
});
onUnmounted(() => {
if (designer) designer.destroy();
});
</script>
<template>
<div ref="container" style="width: 100%; height: 100vh;" />
</template>Angular (v17+ Standalone Component)
import { Component, ElementRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { QRLayoutDesigner } from 'qrlayout-ui';
import 'qrlayout-ui/style.css';
@Component({
standalone: true,
selector: 'app-label-designer',
template: '<div #container style="width: 100%; height: 100vh;"></div>'
})
export class LabelDesignerComponent implements OnInit, OnDestroy {
@ViewChild('container', { static: true }) container!: ElementRef;
private designer?: QRLayoutDesigner;
ngOnInit() {
this.designer = new QRLayoutDesigner({
element: this.container.nativeElement,
onSave: (layout) => console.log('Saved:', layout)
});
}
ngOnDestroy() {
this.designer?.destroy();
}
}Svelte 5 (Runes)
<script lang="ts">
import { onMount } from 'svelte';
import { QRLayoutDesigner } from 'qrlayout-ui';
import 'qrlayout-ui/style.css';
let container = $state<HTMLDivElement | null>(null);
let designer: QRLayoutDesigner | null = null;
onMount(() => {
if (!container) return;
designer = new QRLayoutDesigner({
element: container,
onSave: (layout) => console.log('Saved:', layout)
});
return () => designer?.destroy();
});
</script>
<div bind:this={container} style="width: 100%; height: 100vh;" />Vanilla JavaScript / HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="node_modules/qrlayout-ui/dist/style.css">
<style>
#designer { width: 100%; height: 100vh; }
</style>
</head>
<body>
<div id="designer"></div>
<script type="module">
import { QRLayoutDesigner } from 'qrlayout-ui';
const designer = new QRLayoutDesigner({
element: document.getElementById('designer'),
onSave: (layout) => {
localStorage.setItem('layout', JSON.stringify(layout));
}
});
</script>
</body>
</html>🔗 Related
qrlayout-core— Use the same layout JSON to render PNG, PDF, or ZPL without the UI- GitHub Repository — Full monorepo, issue tracker, and discussions
📄 License
MIT © Shashidhar Naik
