Back to projects

GitHub README

untyper

A simple typewriter for browser and support more
Language
TypeScript
Stars
16
Forks
1
Updated
May 13, 2026

^^_^^ untyper

Looking forward to your feedback

NPM version

untyper feature support

A typewriter utility for the browser. It builds a queue of actions so you can chain type, pause, delete, move, add, image, and then run them with go().

🚀 Features

  • Chained typing actions powered by the Web Animations API cursor
  • Customizable typing speed, start delay, and cursor animation
  • Supports plain text typing as well as inserting HTML elements and images
  • Optional per-element animations when inserting DOM nodes
  • Works with any DOM element

📦 Install

npm install untyper

Usage

import { UnTyper } from 'untyper'

const target = document.querySelector('#text')
const typer = new UnTyper(target, {
  speed: 90,
  startDelay: 300,
  cursorAnimation: {
    kind: 'combined',
    duration: 900,
  },
})

await typer
  .type('Hi there!')
  .pause(400)
  .delete(6)
  .type('untyper!')
  .go()

API

new UnTyper(element, options)

Option Type Default Description
speed number 120 Base delay between characters (ms).
startDelay number 0 Delay before the queue starts (ms).
animationspancontent string ` `
animate.cancel boolean false Hide cursor after the queue completes.
cursorAnimation CursorAnimationOptions — Cursor animation customization.
plugins UnTyperPluginUse[] [] Plugins installed when the instance is created.

type(text, options)

Adds a plain text typing action. HTML is not supported here.

  • text: string
  • options.delay: optional delay (ms) after typing this text

Returns this.

pause(ms)

Adds a pause action.

  • ms: number

Returns this.

delete(charCount, options)

Deletes characters from the current cursor position.

  • charCount: number (must be greater than 0)
  • options.delay: optional delay (ms) after deleting

Returns this.

move(movementArg, options)

Moves the cursor.

  • movementArg: number | null
    • Provide a negative number to move left by that many characters.
    • Use null with options.to = 'start' | 'end' to jump to the start or end.

Returns this.

add(html, options)

Parses HTML and inserts elements with typing animation for text nodes. You can pass options.animation to animate inserted elements.

  • html: string
  • options.delay: optional delay (ms) after adding
  • options.animation: optional ElementAnimation to apply to added elements

Returns this.

image(src, options)

Inserts an img element with optional attributes. You can also pass options.animation to animate the image after insertion.

  • src: string
  • options.alt: string
  • options.className: string
  • options.width: number
  • options.height: number
  • options.attrs: Record<string, string> for additional attributes
  • options.delay: optional delay (ms) after inserting
  • options.animation: optional ElementAnimation to apply to the image

Returns this.

use(plugin, options)

Installs a plugin after the instance is created. A plugin can be a function or an object with name and install(ctx, options).

Returns this.

registerAction(name, handler)

Registers a chainable custom action.

Returns this.

action(name, ...args)

Runs a custom action registered by a plugin or by registerAction.

Returns this.

go()

Runs the queued actions.

Returns Promise<void>.

Animation support

You can apply custom animations to inserted DOM elements using options.animation on add or image.

await typer
  .add('<strong>Spotlight</strong>', {
    animation: {
      keyframes: [
        { opacity: 0, transform: 'translateY(4px)' },
        { opacity: 1, transform: 'translateY(0)' },
      ],
      options: { duration: 300, easing: 'ease-out' },
    },
  })
  .image('/logo.png', {
    alt: 'Brand logo',
    animation: {
      keyframes: [
        { transform: 'scale(0.9)', opacity: 0 },
        { transform: 'scale(1)', opacity: 1 },
      ],
      options: { duration: 250 },
    },
  })
  .go()

Plugin API

Plugins follow the common frontend plugin shape: expose a stable install(ctx, options) entrypoint, keep setup side effects inside install, and register extension points through the provided context instead of reaching into private fields.

import type { UnTyperPlugin } from 'untyper'

const emojiPlugin: UnTyperPlugin<{ suffix?: string }> = {
  name: 'emoji',
  install(ctx, options) {
    ctx.registerAction('emoji', (api, text: string) => {
      api.insertText(`${text}${options?.suffix ?? ''}`)
    })

    ctx.hook('afterRun', ({ root }) => {
      root.setAttribute('data-untyper-complete', 'true')
    })
  },
}

await new UnTyper(target, {
  speed: 80,
  plugins: [[emojiPlugin, { suffix: '!' }]],
})
  .type('Status: ')
  .action('emoji', 'ready')
  .go()

Plugin context:

API Description
ctx.registerAction(name, handler) Adds a named custom action that can be called with action(name, ...args).
ctx.enqueue(steps, options) Adds raw queue steps for advanced plugins. Queue func handlers may be async.
ctx.insertText(text, options) Reuses the built-in text typing behavior.
ctx.insertElement(element, options) Reuses the built-in DOM insertion behavior.
ctx.hook(name, handler) Registers lifecycle hooks: beforeRun, afterRun, beforeStep, afterStep, onError.
ctx.root, ctx.cursor, ctx.options Read-only access to the target element, cursor, and resolved options.

License

MIT License © 2022 Song wuk