Skip to main content

How to Build a Name Tag and Badge Maker with JavaScript Canvas

Name tags, badges and labels are used everywhere — from conferences and events to product packaging and school projects. With Konva you can build a fully interactive badge maker with draggable text, shape templates, color customization and one-click export.

Instructions: Click a badge template to start. Edit the text fields, drag elements to reposition, change colors, and click "Export as PNG" to save your badge.

import Konva from 'konva';

// --- Controls ---
const controls = document.createElement('div');
controls.style.cssText = 'display:flex;gap:8px;align-items:center;margin-bottom:4px;flex-wrap:wrap;';

const bgLabel = document.createElement('label');
bgLabel.textContent = 'Badge Color: ';
const bgColor = document.createElement('input');
bgColor.type = 'color';
bgColor.value = '#3b82f6';
bgLabel.appendChild(bgColor);

const textColorLabel = document.createElement('label');
textColorLabel.textContent = 'Text Color: ';
const textColor = document.createElement('input');
textColor.type = 'color';
textColor.value = '#ffffff';
textColorLabel.appendChild(textColor);

const exportBtn = document.createElement('button');
exportBtn.textContent = 'Export as PNG';
const clearBtn = document.createElement('button');
clearBtn.textContent = 'Reset';

controls.appendChild(bgLabel);
controls.appendChild(textColorLabel);
controls.appendChild(exportBtn);
controls.appendChild(clearBtn);
const container = document.getElementById('container');
container.parentNode.insertBefore(controls, container);

// --- Stage ---
const stageWidth = window.innerWidth;
const stageHeight = window.innerHeight - 40;

const stage = new Konva.Stage({
  container: 'container',
  width: stageWidth,
  height: stageHeight,
});

const layer = new Konva.Layer();
stage.add(layer);

// Badge dimensions
const badgeW = 340;
const badgeH = 220;
const badgeX = (stageWidth - badgeW) / 2;
const badgeY = (stageHeight - badgeH) / 2;

// Badge background
const badge = new Konva.Rect({
  x: badgeX,
  y: badgeY,
  width: badgeW,
  height: badgeH,
  fill: bgColor.value,
  cornerRadius: 16,
  shadowColor: 'rgba(0,0,0,0.15)',
  shadowBlur: 12,
  shadowOffset: { x: 0, y: 4 },
});
layer.add(badge);

// Decorative top stripe
const stripe = new Konva.Rect({
  x: badgeX,
  y: badgeY,
  width: badgeW,
  height: 50,
  fill: 'rgba(0,0,0,0.15)',
  cornerRadius: [16, 16, 0, 0],
});
layer.add(stripe);

// "HELLO" header
const helloText = new Konva.Text({
  x: badgeX,
  y: badgeY + 8,
  width: badgeW,
  text: 'HELLO',
  fontSize: 14,
  fontFamily: 'Arial',
  fontStyle: 'bold',
  fill: 'rgba(255,255,255,0.8)',
  align: 'center',
  letterSpacing: 4,
});
layer.add(helloText);

// "my name is" subtitle
const subtitleText = new Konva.Text({
  x: badgeX,
  y: badgeY + 26,
  width: badgeW,
  text: 'my name is',
  fontSize: 12,
  fontFamily: 'Arial',
  fill: 'rgba(255,255,255,0.6)',
  align: 'center',
});
layer.add(subtitleText);

// Editable name
const nameText = new Konva.Text({
  x: badgeX + 20,
  y: badgeY + 70,
  width: badgeW - 40,
  text: 'Your Name',
  fontSize: 36,
  fontFamily: 'Arial',
  fontStyle: 'bold',
  fill: textColor.value,
  align: 'center',
  draggable: true,
});
layer.add(nameText);

// Editable title/role
const roleText = new Konva.Text({
  x: badgeX + 20,
  y: badgeY + 120,
  width: badgeW - 40,
  text: 'Job Title',
  fontSize: 18,
  fontFamily: 'Arial',
  fill: 'rgba(255,255,255,0.75)',
  align: 'center',
  draggable: true,
});
layer.add(roleText);

// Editable company
const companyText = new Konva.Text({
  x: badgeX + 20,
  y: badgeY + 150,
  width: badgeW - 40,
  text: 'Company',
  fontSize: 16,
  fontFamily: 'Arial',
  fill: 'rgba(255,255,255,0.55)',
  align: 'center',
  draggable: true,
});
layer.add(companyText);

// Small circle decoration
const circle = new Konva.Circle({
  x: badgeX + badgeW - 30,
  y: badgeY + badgeH - 30,
  radius: 14,
  fill: 'rgba(255,255,255,0.2)',
});
layer.add(circle);

// Transformer for selection
const tr = new Konva.Transformer({
  rotateEnabled: false,
  borderStrokeWidth: 1,
  anchorSize: 8,
});
layer.add(tr);

// Select on click
stage.on('click tap', function (e) {
  const target = e.target;
  if (target === stage || target === badge || target === stripe || target === circle) {
    tr.nodes([]);
    return;
  }
  if (target.draggable()) {
    tr.nodes([target]);
  }
});

// Double-click to edit text
function enableTextEdit(textNode) {
  textNode.on('dblclick dbltap', () => {
    textNode.hide();
    tr.hide();
    const textPosition = textNode.absolutePosition();
    const stageBox = stage.container().getBoundingClientRect();
    const input = document.createElement('input');
    input.type = 'text';
    input.value = textNode.text();
    input.style.position = 'absolute';
    input.style.top = stageBox.top + textPosition.y + 'px';
    input.style.left = stageBox.left + textPosition.x + 'px';
    input.style.width = textNode.width() + 'px';
    input.style.fontSize = textNode.fontSize() + 'px';
    input.style.fontFamily = textNode.fontFamily();
    input.style.textAlign = textNode.align();
    input.style.border = '2px solid #3b82f6';
    input.style.borderRadius = '4px';
    input.style.padding = '2px 4px';
    input.style.outline = 'none';
    input.style.background = '#fff';
    input.style.zIndex = '1000';
    document.body.appendChild(input);
    input.focus();
    input.select();

    function finish() {
      textNode.text(input.value);
      textNode.show();
      tr.show();
      tr.forceUpdate();
      document.body.removeChild(input);
    }
    input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') finish();
    });
    input.addEventListener('blur', finish);
  });
}

enableTextEdit(nameText);
enableTextEdit(roleText);
enableTextEdit(companyText);

// Controls
bgColor.addEventListener('input', () => {
  badge.fill(bgColor.value);
});

textColor.addEventListener('input', () => {
  nameText.fill(textColor.value);
});

exportBtn.addEventListener('click', () => {
  // hide transformer for clean export
  tr.nodes([]);
  const dataURL = stage.toDataURL({
    x: badgeX - 4,
    y: badgeY - 4,
    width: badgeW + 8,
    height: badgeH + 8,
    pixelRatio: 3,
  });
  const link = document.createElement('a');
  link.download = 'badge.png';
  link.href = dataURL;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
});

clearBtn.addEventListener('click', () => {
  nameText.text('Your Name');
  roleText.text('Job Title');
  companyText.text('Company');
  bgColor.value = '#3b82f6';
  badge.fill('#3b82f6');
  textColor.value = '#ffffff';
  nameText.fill('#ffffff');
  tr.nodes([]);
});