← Typing

Blinking Cursor

A caret at the end of your text, blinking as if waiting for the next keystroke. Choose a thin bar, a terminal block or an underscore, and a soft fade or a hard on-off blink.

Your move

~/site $ deploy

READY

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-blinking-cursor to a heading or line of text. The caret goes at the end.

Options

Add any of these attributes to the same element.

data-shapebar (default), block or underscore.
data-blinksmooth (default) fades in and out. hard switches on and off like a terminal.
data-rateTime for one full blink. Default 1060 ms.
data-colorThe caret’s colour. Default the text’s.

It only blinks while on screen. With reduced motion turned on the caret stays steady.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Blinking Cursor: a caret at the end of your text, blinking as if waiting
    // for the next keystroke. Pick a thin bar, a terminal block or an
    // underscore, and a soft fade or a hard on-off blink.
    // Usage: <h1 data-blinking-cursor>Your move</h1>
    // Optional: data-shape ("bar" (default), "block" or "underscore"),
    // data-blink ("smooth" (default) or "hard"), data-rate (ms per blink,
    // default 1060), data-color (the caret's colour; default the text's).
    var DEFAULTS = { rate: 1060 };

    var css =
      '.bcr{display:inline-block;margin-left:0.06em;background:currentColor;border-radius:0.02em;pointer-events:none}' +
      '.bcr-bar{width:0.07em;height:1em;vertical-align:-0.12em}' +
      '.bcr-block{width:0.56em;height:1.05em;vertical-align:-0.18em;opacity:.85}' +
      '.bcr-underscore{width:0.55em;height:0.08em;vertical-align:-0.06em}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function add(el) {
      var d = el.dataset;
      var shape = /^(block|underscore)$/.test(d.shape) ? d.shape : 'bar';
      var caret = document.createElement('span');
      caret.className = 'bcr bcr-' + shape;
      caret.setAttribute('aria-hidden', 'true');
      if (d.color) caret.style.background = d.color;
      el.appendChild(caret);
      return caret;
    }

    function start(el, caret) {
      var d = el.dataset;
      var half = (+d.rate || DEFAULTS.rate) / 2;
      var hard = d.blink === 'hard';
      var base = parseFloat(getComputedStyle(caret).opacity) || 1;
      return anime.createTimeline({ loop: true })
        // On...
        .add(caret, { opacity: base, duration: hard ? 1 : half * 0.3, ease: 'out(2)' }, 0)
        // ...hold...
        .add(caret, { opacity: 0, duration: hard ? 1 : half * 0.45, ease: 'inOut(2)' }, half)
        // ...off, and hold until the next blink.
        .add(caret, { opacity: 0, duration: 1 }, half * 2 - 1);
    }

    function init() {
      var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      document.querySelectorAll('[data-blinking-cursor]').forEach(function (el) {
        if (el._bcr) return;
        var caret = (el._bcr = add(el));
        // Reduced motion: a steady caret.
        if (reduced) return;
        var tl = null;
        // Blink only while visible, to save battery.
        new IntersectionObserver(function (entries) {
          if (!entries[0].isIntersecting) { if (tl) tl.pause(); return; }
          if (tl) tl.play();
          else tl = start(el, caret);
        }).observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<h1 data-blinking-cursor>Your move</h1>

<!-- a terminal-style block: -->
<p data-blinking-cursor data-shape="block" data-blink="hard">~/site $</p>