v2.6.0

<bmx-label-designer>

A label designer for the page: shipping labels, product and shelf labels, asset tags, name badges, sample tubes, price tickets. Lay out text, barcodes (Code 128, GS1-128, EAN, UPC, ITF, Code 39, QR, Data Matrix, PDF417), pictures and shapes on a label measured in millimetres or inches; bind them to fields; load a CSV or JSON file and print a label for every row - on A4 or Letter sheets of die-cut labels, or on a thermal printer's roll as PDF or as ZPL the printer runs natively.

11 properties · 4 events · 12 methods · 5 parts

Example

Choose an element to edit it; drag to move, pull a handle to resize. The Data tab steps through the records; Sheet preview shows every page; Print and Export make the whole job.

Show markup
<div role="group" aria-label="Sample labels" style="display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px">
  <button type="button" class="ex-label-pick" data-sample="shipping" aria-pressed="true">Shipping label (roll)</button>
  <button type="button" class="ex-label-pick" data-sample="shelf" aria-pressed="false">Shelf labels (A4 sheet)</button>
  <button type="button" class="ex-label-pick" data-sample="asset" aria-pressed="false">Asset tags (300 dpi roll)</button>
</div>
<bmx-label-designer id="ex-labels" style="--bmx-label-designer-height: 44rem"></bmx-label-designer>
<p class="note" id="ex-labels-out" role="status" style="margin: 12px 0 0">Choose an element to edit it; drag to move, pull a handle to resize. The Data tab steps through the records; Sheet preview shows every page; Print and Export make the whole job.</p>

<script type="module">
  await customElements.whenDefined('bmx-label-designer');
  const designer = document.getElementById('ex-labels');
  const out = document.getElementById('ex-labels-out');

  // A small mark for the asset tags, drawn here so the example needs no picture files.
  const mark =
    'data:image/svg+xml;utf8,' +
    encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" rx="9" fill="#111"/><path d="M12 9v22h17" fill="none" stroke="#fff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="27" cy="13" r="4" fill="#fff"/></svg>');

  const samples = {
    // A 100 x 150 mm thermal shipping label: sender, service panel, address, QR, tracking barcode and an SSCC.
    shipping: {
      template: {
        name: 'Shipping label',
        unit: 'mm',
        width: 100,
        height: 150,
        dpi: 203,
        stock: 'roll-100x150',
        fields: [{ name: 'name', sample: 'Ada Lovelace' }],
        serial: { start: 1, step: 1, digits: 0 },
        elements: [
          { id: 'from', type: 'text', x: 4, y: 4, width: 60, height: 17, text: 'FROM\nLarkfield Supply Co.\n12 Mill Lane, Bristol BS1 4XE', size: 8, fit: 'shrink' },
          { id: 'service', type: 'text', x: 68, y: 4, width: 28, height: 17, text: '{{service|upper}}', size: 16, bold: true, align: 'center', valign: 'middle', color: '#ffffff', background: '#000000' },
          { id: 'rule-1', type: 'line', x: 0, y: 23, width: 100, height: 1, orientation: 'horizontal', thickness: 1.5 },
          { id: 'to-caption', type: 'text', x: 4, y: 26, width: 30, height: 4, text: 'SHIP TO', size: 7, bold: true },
          { id: 'name', type: 'text', x: 4, y: 31, width: 92, height: 8, text: '{{name}}', size: 16, bold: true },
          { id: 'address', type: 'text', x: 4, y: 40, width: 62, height: 18, text: '{{address}}\n{{city}}', size: 12 },
          { id: 'postcode', type: 'text', x: 4, y: 59, width: 62, height: 13, text: '{{postcode|upper}}', size: 30, bold: true },
          { id: 'order', type: 'text', x: 4, y: 74, width: 62, height: 6, text: 'Order {{order}} · {{weight}} kg · parcel {{index}} of {{count}}', size: 8 },
          { id: 'qr', type: 'barcode', x: 70, y: 52, width: 26, height: 26, symbology: 'qr', value: 'https://track.example.com/{{tracking}}' },
          { id: 'rule-2', type: 'line', x: 0, y: 84, width: 100, height: 1, orientation: 'horizontal', thickness: 1.5 },
          { id: 'tracking', type: 'barcode', x: 8, y: 88, width: 84, height: 28, symbology: 'code128', value: '{{tracking}}' },
          { id: 'rule-3', type: 'line', x: 0, y: 120, width: 100, height: 1, orientation: 'horizontal', thickness: 1.5 },
          { id: 'sscc', type: 'barcode', x: 4, y: 123, width: 92, height: 24, symbology: 'gs1-128', value: '(00){{sscc|check}}' },
        ],
      },
      records: `name,address,city,postcode,service,tracking,sscc,order,weight
Ada Lovelace,4 Orchard Row,Bath,BA1 2QH,Express,LK204518860GB,35012345000004518,SO-10421,1.2
Grace Hopper,"Flat 3, 19 Harbour Street",Whitby,YO21 3PT,Standard,LK204518877GB,35012345000004519,SO-10422,0.6
Alan Turing,The Old Forge,Wilmslow,SK9 5AF,Express,LK204518891GB,35012345000004520,SO-10423,3.4
Katherine Johnson,27 Kingfisher Way,Norwich,NR3 1AU,Next day,LK204518902GB,35012345000004521,SO-10424,2.0
Mary Somerville,8 Cliff Terrace,Burntisland,KY3 9AZ,Standard,LK204518916GB,35012345000004522,SO-10425,0.9`,
    },
    // Shelf-edge labels on an A4 sheet of 21: product, size, price, EAN-13 and a unit price.
    shelf: {
      template: {
        name: 'Shelf labels',
        unit: 'mm',
        width: 63.5,
        height: 38.1,
        dpi: 300,
        corner: 2,
        stock: 'a4-21',
        elements: [
          { id: 'product', type: 'text', x: 3, y: 2.5, width: 57.5, height: 9, text: '{{product}}', size: 12, bold: true },
          { id: 'detail', type: 'text', x: 3, y: 11.5, width: 34, height: 5, text: '{{size}} · {{sku}}', size: 7, color: '#444444' },
          { id: 'price', type: 'text', x: 33, y: 16.5, width: 27.5, height: 12, text: '£{{price|number:2}}', size: 22, bold: true, align: 'right', valign: 'middle' },
          { id: 'ean', type: 'barcode', x: 3, y: 18, width: 29, height: 17.5, symbology: 'ean13', value: '{{ean}}' },
          { id: 'unit', type: 'text', x: 33, y: 29, width: 27.5, height: 5, text: '{{unit}}', size: 6.5, align: 'right', color: '#444444' },
          { id: 'offer', type: 'text', x: 41, y: 11, width: 19.5, height: 5, text: 'OFFER', size: 7, bold: true, align: 'center', valign: 'middle', color: '#ffffff', background: '#b91c1c', printIf: 'offer' },
        ],
      },
      records: `product,size,sku,price,ean,unit,offer
Stoneground wholemeal flour,1.5 kg,BK-0141,2.35,501234500141,£1.57 per kg,
Cold-pressed rapeseed oil,500 ml,OL-0207,4.80,501234500207,£9.60 per litre,yes
Wildflower honey,340 g,HN-0033,6.25,501234500033,£18.38 per kg,
Rolled jumbo oats,1 kg,CR-0518,1.95,501234500518,£1.95 per kg,yes
Sea salt flakes,250 g,SP-0090,3.10,501234500090,£12.40 per kg,
Dark roast coffee beans,1 kg,CF-0712,14.50,501234500712,£14.50 per kg,
Smoked paprika,45 g,SP-0115,2.20,501234500115,£4.89 per 100 g,
Organic red lentils,750 g,PL-0304,2.65,501234500304,£3.53 per kg,yes`,
    },
    // Asset tags, 57 x 32 mm at 300 dpi: a serial number that counts up, as Data Matrix and Code 128.
    asset: {
      template: {
        name: 'Asset tags',
        unit: 'mm',
        width: 57,
        height: 32,
        dpi: 300,
        corner: 2,
        stock: 'roll-57x32',
        serial: { start: 1040, step: 1, digits: 6 },
        elements: [
          { id: 'mark', type: 'image', x: 3, y: 3, width: 9, height: 9, src: mark, alt: 'Larkfield mark' },
          { id: 'owner', type: 'text', x: 14, y: 3, width: 40, height: 4.5, text: 'LARKFIELD SUPPLY CO.', size: 9, bold: true },
          { id: 'return', type: 'text', x: 14, y: 7.5, width: 40, height: 5.5, text: 'Company property. If found,\ncall 0117 496 0000', size: 6 },
          { id: 'matrix', type: 'barcode', x: 3, y: 14.5, width: 14, height: 14, symbology: 'datamatrix', value: 'LSC-{{serial}}' },
          { id: 'bars', type: 'barcode', x: 19, y: 14.5, width: 35, height: 9, symbology: 'code128', value: 'LSC{{serial}}', showText: false },
          { id: 'number', type: 'text', x: 19, y: 24.5, width: 35, height: 4.5, text: 'LSC-{{serial}} · {{category}}', size: 7.5, bold: true, font: 'mono' },
        ],
      },
      records: `category,location
Laptop,Studio
Laptop,Studio
Monitor,Studio
Monitor,Workshop
Printer,Office
Camera,Studio
Tablet,Workshop
Router,Office
Scanner,Office
Laptop,Workshop`,
    },
  };

  const show = name => {
    designer.template = samples[name].template;
    designer.records = samples[name].records;
    designer.mode = 'design';
    document.querySelectorAll('.ex-label-pick').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.sample === name)));
  };
  document.querySelectorAll('.ex-label-pick').forEach(b => b.addEventListener('click', () => show(b.dataset.sample)));
  show('shipping');

  designer.addEventListener('bmxChange', e => (out.textContent = `bmxChange: ${e.detail.reason}. ${e.detail.template.elements.length} elements - save e.detail.template to keep the design.`));
  designer.addEventListener('bmxRecordChange', e => (out.textContent = `bmxRecordChange: record ${e.detail.index + 1}.`));
  designer.addEventListener('bmxExport', e => {
    // Cancel to keep the file from downloading - to send ZPL straight to a printer, for instance.
    out.textContent = `bmxExport: ${e.detail.filename} (${typeof e.detail.data === 'string' ? `${e.detail.data.length} characters` : `${e.detail.data.size} bytes`}).`;
  });
</script>
<bmx-label-designer id="labels"></bmx-label-designer>
<script>
  labels.template = { unit: 'mm', width: 100, height: 50, elements: [
    { type: 'text', x: 4, y: 4, width: 92, height: 10, text: '{{name}}', size: 16, bold: true },
    { type: 'barcode', x: 4, y: 18, width: 70, height: 26, symbology: 'code128', value: '{{sku}}' },
  ] };
  labels.records = 'name,sku\nBlue mug,MUG-001\nRed mug,MUG-002';
</script>

Every output comes from one layout (label-layout.ts), so the design, the sheet preview, the printout, the PDF and the ZPL agree. Barcodes are sized to whole printer dots at the template's resolution, so what is designed is what scans.

Accessibility: the design surface is focusable; Page Down and Page Up step through the elements, the arrow keys move the chosen one (Control resizes) and what it is and where it sits is announced. The Layers tab is a listbox of every element, the Properties tab a form of labelled fields, and every tool is a labelled button.

Properties

PropertyAttributeTypeDefaultDescription
copies copies number 1 Copies of each record. Mutable.
gridSize grid-size number — The grid, in the template's unit. Default 1 mm, 0.05 in or 4 pt.
label label string — The designer's accessible name. Default: the template's name.
mode mode 'design' | 'preview' 'design' design to edit, preview to see the sheets as they will print. Mutable.
readonly readonly boolean false Look and print only: no editing.
records records readonly BmxLabelRecord[] | string — The data to print: an array of records, or CSV (or JSON) text.
skip skip number 0 Labels already used on the first sheet, so a part-used sheet can be finished. Mutable.
snap snap boolean true Snap to the grid and to other elements while dragging. Mutable.
stocks property only readonly Record<string, unknown>[] — Label stocks of your own, besides the built-in sheets and rolls.
strings strings Partial<BmxLabelDesignerStrings> | string — Words to show instead of the English ones, as an object or JSON.
template template BmxLabelTemplate | Record<string, unknown> | string — The template: an object or JSON. Mutable: it is replaced as the label is edited.

Events

EventDetailDescription
bmxChange BmxLabelChangeDetail Fired when the template is edited, with the whole of it.
bmxExport BmxLabelExportDetail Fired when an export is made from the Export menu. Cancel it to stop the download and use data yourself.
bmxRecordChange BmxLabelRecordDetail Fired when another record is shown.
bmxSelectionChange BmxLabelSelectionDetail Fired when the selected elements change.

Methods

MethodSignatureDescription
addElement addElement(type: BmxLabelElementType, properties?: Record<string, unknown>) => Promise<string> Adds an element (text, barcode, image, box, ellipse, line) with any of its properties; returns its id.
exportPdf exportPdf(options?: BmxLabelExportOptions) => Promise<Blob> The job as a PDF: on the template's sheets or roll, or one label per page.
exportPng exportPng() => Promise<Blob> The label showing as a PNG at the printer's resolution.
exportSvg exportSvg() => Promise<string> The label showing as SVG, pictures included.
exportZpl exportZpl(options?: BmxLabelExportOptions) => Promise<string> The job as ZPL for a thermal printer: one format per label, pictures as black and white dots.
getTemplate getTemplate() => Promise<BmxLabelTemplate> The template as it stands.
loadData loadData(text: string) => Promise<number> Loads CSV or JSON text as the data; returns how many records it has.
print print(options?: BmxLabelExportOptions) => Promise<void> Prints the job from the browser: the pages exactly as the sheet preview shows them, at their real size.
redo redo() => Promise<void>
selectElements selectElements(ids: string[]) => Promise<void> Selects elements by id.
showRecord showRecord(index: number) => Promise<void> Shows a record (0-based).
undo undo() => Promise<void>

CSS shadow parts

PartDescription
canvas The scrolling area the label sits in.
label The label itself.
panel The Properties, Layers and Data tabs.
status The status line along the bottom.
toolbar The tools along the top.

CSS custom properties

PropertyDescription
--bmx-label-designer-accent Selection, handles and guides.
--bmx-label-designer-desk The background around the label.
--bmx-label-designer-height The designer's height (on a wide screen; narrow, it grows to fit).
--bmx-label-designer-panel-width The width of the side panel.
--bmx-label-designer-radius Corner radius of the designer.