Content & Media

Free PDF Download Card Widget

A polished document card with file icon, details, and a download button.

A clean card that presents any PDF with a file icon, name, file size, short description, and a prominent download button. Perfect for sharing whitepapers, menus, brochures, price lists, and lead magnets on any page. Paste one script tag where you want the card to appear and point it at your file.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/pdf-download-card.js" data-hl-href="/sample.pdf" data-hl-name="2026 Small Business Website Checklist" data-hl-size="PDF · 1.8 MB" data-hl-description="A 24-point checklist covering speed, SEO, security, and conversion — everything we review before launching a client site." async></script>
<!-- Free PDF Download Card widget by haynes lab — https://hayneslab.dev/tools/widgets/pdf-download-card -->
Inline (self-contained)html
<!-- Free PDF Download Card widget by haynes lab — https://hayneslab.dev/tools/widgets/pdf-download-card -->
<script data-hl-href="/sample.pdf" data-hl-name="2026 Small Business Website Checklist" data-hl-size="PDF · 1.8 MB" data-hl-description="A 24-point checklist covering speed, SEO, security, and conversion — everything we review before launching a client site.">
/*!
 * haynes lab — PDF Download Card widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/pdf-download-card
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-pdf-download-card';
  var STYLE_ID = 'hlw-pdf-download-card-styles';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/pdf-download-card.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 480px; margin: 0; padding: 20px 20px 26px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; display: flex; gap: 16px; align-items: flex-start; }' +
      '.' + PREFIX + '__icon { flex: 0 0 auto; width: 48px; height: 56px; position: relative; color: var(--hlw-accent); }' +
      '.' + PREFIX + '__icon svg { display: block; width: 100%; height: 100%; }' +
      '.' + PREFIX + '__badge { position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%);' +
      ' font-size: 9px; font-weight: 700; letter-spacing: 0.04em; color: #ffffff;' +
      ' background: var(--hlw-accent); border-radius: 3px; padding: 1px 4px; line-height: 1.4; }' +
      '.' + PREFIX + '__body { flex: 1 1 auto; min-width: 0; }' +
      '.' + PREFIX + '__name { margin: 0; font-size: 16px; font-weight: 600; color: #111827;' +
      ' overflow-wrap: anywhere; }' +
      '.' + PREFIX + '__size { font-size: 13px; color: #6b7280; margin-top: 2px; }' +
      '.' + PREFIX + '__description { margin: 8px 0 0; font-size: 14px; color: #4b5563; }' +
      '.' + PREFIX + '__button { display: inline-flex; align-items: center; gap: 8px; margin-top: 14px;' +
      ' padding: 9px 18px; border: 0; border-radius: 8px; background: var(--hlw-accent); color: #ffffff;' +
      ' font-family: inherit; font-size: 14px; font-weight: 600; line-height: 1.2;' +
      ' text-decoration: none; cursor: pointer; }' +
      '.' + PREFIX + '__button svg { display: block; }' +
      '.' + PREFIX + '__button:hover { filter: brightness(0.92); }' +
      '.' + PREFIX + '__button:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
      ' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
      '@media (max-width: 420px) { .' + PREFIX + ' { flex-direction: column; padding-bottom: 28px; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function fileIconSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 48 56');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    var body = document.createElementNS(svgNS, 'path');
    body.setAttribute('d', 'M8 2h24l14 14v36a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z');
    body.setAttribute('fill', 'none');
    body.setAttribute('stroke', 'currentColor');
    body.setAttribute('stroke-width', '2.5');
    body.setAttribute('stroke-linejoin', 'round');
    var fold = document.createElementNS(svgNS, 'path');
    fold.setAttribute('d', 'M32 2v14h14');
    fold.setAttribute('fill', 'none');
    fold.setAttribute('stroke', 'currentColor');
    fold.setAttribute('stroke-width', '2.5');
    fold.setAttribute('stroke-linejoin', 'round');
    svg.appendChild(body);
    svg.appendChild(fold);
    return svg;
  }

  function downloadIconSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 16 16');
    svg.setAttribute('width', '15');
    svg.setAttribute('height', '15');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', 'M8 1v9m0 0L4.5 6.5M8 10l3.5-3.5M2 12.5v1.5a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-1.5');
    path.setAttribute('fill', 'none');
    path.setAttribute('stroke', 'currentColor');
    path.setAttribute('stroke-width', '1.8');
    path.setAttribute('stroke-linecap', 'round');
    path.setAttribute('stroke-linejoin', 'round');
    svg.appendChild(path);
    return svg;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function nameFromHref(href) {
    try {
      var path = href.split('?')[0].split('#')[0];
      var last = path.substring(path.lastIndexOf('/') + 1);
      return decodeURIComponent(last) || 'Download PDF';
    } catch (e) {
      return 'Download PDF';
    }
  }

  function build(script) {
    var href = script.getAttribute('data-hl-href');
    if (!href) {
      console.warn('[haynes lab pdf-download-card] Missing required data-hl-href attribute (URL of the PDF). Nothing rendered.');
      return;
    }
    var name = script.getAttribute('data-hl-name') || nameFromHref(href);
    var size = script.getAttribute('data-hl-size') || '';
    var description = script.getAttribute('data-hl-description') || '';
    var accent = script.getAttribute('data-hl-accent') || '#f97316';

    injectStyles();

    var root = el('div', PREFIX);
    root.style.setProperty('--hlw-accent', accent);

    var icon = el('div', PREFIX + '__icon');
    icon.appendChild(fileIconSvg());
    icon.appendChild(el('span', PREFIX + '__badge', 'PDF'));
    root.appendChild(icon);

    var body = el('div', PREFIX + '__body');
    body.appendChild(el('p', PREFIX + '__name', name));
    if (size) body.appendChild(el('div', PREFIX + '__size', size));
    if (description) body.appendChild(el('p', PREFIX + '__description', description));

    var button = el('a', PREFIX + '__button');
    button.href = href;
    button.setAttribute('download', '');
    button.setAttribute('aria-label', 'Download ' + name + ' (PDF)');
    button.appendChild(downloadIconSvg());
    button.appendChild(document.createTextNode('Download'));
    body.appendChild(button);
    root.appendChild(body);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/pdf-download-card';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab pdf-download-card] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-href(required)URL of the PDF file the download button points to.
data-hl-name(none)Title shown on the card; falls back to the filename from the URL when omitted.
data-hl-size(none)Human-readable file size shown under the name, e.g. "2.4 MB"; hidden when omitted.
data-hl-description(none)Short paragraph describing the document; hidden when omitted.
data-hl-accent"#f97316"Color of the download button, file icon, and PDF badge.

Frequently asked questions

How do I install the PDF Download Card?

Paste the script tag where you want the card to appear and set data-hl-href to your PDF's URL. The card renders inline right where the script tag sits, and the download button uses the browser's native download behavior.

How do I customize the text and colors?

Use data-hl-name for the document title, data-hl-size for the file size line, data-hl-description for a short summary, and data-hl-accent with any hex color to recolor the button, icon, and PDF badge.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies. Paste the snippet into any HTML block, theme file, or custom code section and it works anywhere scripts are allowed.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch