← Numbers

Nixie Tube

Each digit glows orange inside its own glass tube, with the other nine wire cathodes faintly visible behind it. When a digit changes the tube flickers through a few numbers before the new one settles and glows, just like the real thing.

98.6

How to use it

  1. Paste the snippet just before </head>.
  2. Put the number in an element with data-nixie-tube, or leave it empty and add data-clock for the time. Size it with font-size: each tube is about 0.8em wide and 1.5em tall.

Options

Add any of these attributes to the same element.

data-values="A|B|C"Several values to flicker between in turn.
data-holdHow long each value stays. Default 2200 ms.
data-clockShow the visitor’s local time, ticking every second. data-clock="12" for a 12-hour clock.
data-flickerHow many digits a tube flashes through on a change. Default 6; 0 switches cleanly.
data-colorGlow colour. Default #ff7a1a.
data-loopWith a single value: power off and flicker on again every few seconds.

The tubes are dark glass, so they look best on a dark background. Colons and dots show as small glowing neon dots.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Nixie Tube: each digit glows orange inside its own glass tube, with the
    // other nine cathodes faintly visible behind it. When a digit changes, the
    // tube flickers through a few numbers before the new one settles and glows.
    // Usage: <span data-nixie-tube>1984</span>
    //    or: <span data-nixie-tube data-clock></span>   (live local time)
    // Optional: data-values (several values to flicker between, "A|B|C"),
    // data-hold (ms each value stays, default 2200), data-clock (show the time,
    // ticking every second; data-clock="12" for 12-hour), data-color (glow
    // colour, default #ff7a1a), data-flicker (how many digits it flashes
    // through on a change, default 6), data-loop (with a single value: power
    // off and flicker on again every few seconds).
    // Size it with font-size: a tube is about 0.8em wide and 1.5em tall.
    var DEFAULTS = { hold: 2200, color: '#ff7a1a', flicker: 6 };

    var css =
      '.nxt{display:inline-flex;align-items:flex-end;gap:.1em;line-height:1;vertical-align:middle;--nxt-c:#ff7a1a}' +
      '.nxt-unit{display:flex;flex-direction:column;align-items:center}' +
      '.nxt-tube{position:relative;width:.8em;height:1.34em;border-radius:.4em .4em .1em .1em;overflow:hidden;' +
      'background:radial-gradient(ellipse 70% 55% at 50% 48%,color-mix(in srgb,var(--nxt-c) var(--nxt-warm,0%),transparent),transparent 72%),' +
      'linear-gradient(90deg,rgba(255,255,255,.1),rgba(255,255,255,.02) 28%,transparent 60%,rgba(255,255,255,.08)),#120b08;' +
      'box-shadow:inset 0 0 0 .02em rgba(255,255,255,.2),inset 0 .12em .2em rgba(255,255,255,.06),0 .04em .1em rgba(0,0,0,.4)}' +
      // The anode mesh, a fine grid of dots across the tube.
      '.nxt-tube::before{content:"";position:absolute;inset:.12em .08em .1em;border-radius:.3em .3em 0 0;' +
      'background:radial-gradient(circle,rgba(255,170,110,.13) 32%,transparent 40%) 0 0/.075em .075em;opacity:.9}' +
      // Glass highlight.
      '.nxt-tube::after{content:"";position:absolute;left:.1em;top:.18em;bottom:.3em;width:.07em;border-radius:.05em;' +
      'background:linear-gradient(rgba(255,255,255,.35),rgba(255,255,255,.04));pointer-events:none}' +
      '.nxt-n{position:absolute;inset:0;display:grid;place-items:center;padding-top:.04em;' +
      'font-family:Bahnschrift,"DIN Alternate","Roboto Condensed","Arial Narrow",sans-serif;font-weight:300;font-size:1em;' +
      'color:rgba(255,190,140,.07);transition:none}' +
      '.nxt-n.nxt-on{color:#fff1dc;text-shadow:0 0 .03em #fff,0 0 .07em var(--nxt-c),0 0 .16em var(--nxt-c),0 0 .36em var(--nxt-c)}' +
      '.nxt-base{width:.86em;height:.14em;margin-top:-.02em;border-radius:0 0 .08em .08em;background:linear-gradient(#3a3a40,#121214)}' +
      '.nxt-sep{display:flex;flex-direction:column;justify-content:center;gap:.28em;height:1.34em;margin-bottom:.14em}' +
      '.nxt-sep i{display:block;width:.11em;height:.11em;border-radius:50%;background:#fff1dc;' +
      'box-shadow:0 0 .06em var(--nxt-c),0 0 .18em var(--nxt-c);opacity:var(--nxt-o,1)}' +
      '.nxt-sp{width:.25em}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }

    function clockText(mode) {
      var d = new Date(), h = d.getHours();
      if (mode === '12') h = h % 12 || 12;
      var p = function (n) { return (n < 10 ? '0' : '') + n; };
      return p(h) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
    }

    function pattern(text) { return text.replace(/\d/g, '#'); }

    function build(x, text) {
      x.el.innerHTML = '';
      x.tubes = [];
      x.seps = [];
      x.pattern = pattern(text);
      for (var i = 0; i < text.length; i++) {
        var ch = text.charAt(i), node;
        if (/\d/.test(ch)) {
          node = document.createElement('span');
          node.className = 'nxt-unit';
          var tube = document.createElement('span');
          tube.className = 'nxt-tube';
          var nums = [];
          // Real tubes stack the cathodes front to back in this order.
          [1, 6, 2, 7, 5, 0, 4, 9, 8, 3].forEach(function (n) {
            var s = document.createElement('span');
            s.className = 'nxt-n';
            s.textContent = n;
            tube.appendChild(s);
            nums[n] = s;
          });
          node.appendChild(tube);
          var base = document.createElement('span');
          base.className = 'nxt-base';
          node.appendChild(base);
          x.tubes.push({ tube: tube, nums: nums, digit: null, timer: 0 });
        } else if (ch === ':' || ch === '.') {
          node = document.createElement('span');
          node.className = 'nxt-sep';
          node.innerHTML = ch === ':' ? '<i></i><i></i>' : '<i style="margin-top:auto"></i>';
          x.seps.push(node);
        } else {
          node = document.createElement('span');
          node.className = 'nxt-sp';
        }
        node.setAttribute('aria-hidden', 'true');
        x.el.appendChild(node);
      }
    }

    function light(t, n, alpha) {
      t.nums.forEach(function (s, i) {
        s.classList.toggle('nxt-on', i === n);
        s.style.opacity = i === n ? alpha : '';
      });
      t.tube.style.setProperty('--nxt-warm', n === null ? '0%' : Math.round(alpha * 22) + '%');
    }

    // Flash through a few random cathodes, then settle on the digit.
    function flicker(x, t, digit, delay) {
      clearTimeout(t.timer);
      if (x.still) { light(t, digit, 1); return; }
      var left = x.flicker;
      var tick = function () {
        if (left-- > 0) {
          light(t, Math.floor(Math.random() * 10), 0.45 + Math.random() * 0.55);
          t.timer = setTimeout(tick, 35 + Math.random() * 40);
        } else {
          light(t, digit, 0.55);
          t.timer = setTimeout(function () { light(t, digit, 1); }, 70);
        }
      };
      t.timer = setTimeout(tick, delay);
    }

    function show(x, text) {
      x.el.setAttribute('aria-label', text);
      if (pattern(text) !== x.pattern) build(x, text);
      var digits = text.replace(/\D/g, ''), n = 0;
      x.tubes.forEach(function (t, i) {
        var dg = +digits.charAt(i);
        if (t.digit === dg) return;
        t.digit = dg;
        flicker(x, t, dg, n++ * 60 + Math.random() * 40);
      });
      x.seps.forEach(function (s) { s.style.setProperty('--nxt-o', 1); });
    }

    function off(x) {
      x.tubes.forEach(function (t) { clearTimeout(t.timer); t.digit = null; light(t, null, 0); });
      x.seps.forEach(function (s) { s.style.setProperty('--nxt-o', 0.12); });
    }

    function setup(el) {
      var d = el.dataset;
      var x = {
        el: el, values: d.values ? d.values.split('|') : [el.textContent.trim() || '0'], index: 0,
        clock: d.clock !== undefined ? d.clock : null, hold: +d.hold || DEFAULTS.hold,
        flicker: d.flicker !== undefined ? +d.flicker : DEFAULTS.flicker,
        loop: d.loop !== undefined && d.loop !== 'false', still: reduced(), visible: false, timer: 0, started: false
      };
      el.classList.add('nxt');
      el.setAttribute('role', 'img');
      if (d.color) el.style.setProperty('--nxt-c', d.color);
      var first = x.clock !== null ? clockText(x.clock) : x.values[0];
      build(x, first);
      el.setAttribute('aria-label', first);
      if (x.still) show(x, first);
      else off(x);
      return x;
    }

    function step(x) {
      clearTimeout(x.timer);
      if (!x.visible) return;
      if (x.clock !== null) {
        show(x, clockText(x.clock));
        x.timer = setTimeout(function () { step(x); }, 1000 - (Date.now() % 1000) + 20);
      } else if (x.values.length > 1) {
        show(x, x.values[x.index]);
        x.index = (x.index + 1) % x.values.length;
        x.timer = setTimeout(function () { step(x); }, x.hold);
      } else {
        show(x, x.values[0]);
        if (x.loop) x.timer = setTimeout(function () { off(x); x.timer = setTimeout(function () { step(x); }, 500); }, x.hold + 1200);
      }
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          var x = e.target._nxt;
          x.visible = e.isIntersecting;
          if (x.visible) {
            if (!x.started || x.clock !== null || x.values.length > 1 || x.loop) step(x);
            x.started = true;
          } else clearTimeout(x.timer);
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-nixie-tube]').forEach(function (el) {
        if (el._nxt) return;
        var x = (el._nxt = setup(el));
        if (x.still) {
          if (x.clock !== null) setInterval(function () { show(x, clockText(x.clock)); }, 1000);
          return;
        }
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<p style="font-size: 72px"><span data-nixie-tube>1984</span></p>

<!-- a live clock: -->
<span data-nixie-tube data-clock></span>