v2.6.0

<bmx-terminal>

A terminal in the page: shells, build logs, deploy consoles, device consoles, a REPL, a container's exec. It understands the control sequences programs write (VT220 and xterm: colours to 24-bit, cursor movement, the alternate screen of editors and pagers, scroll regions, bracketed paste, mouse reporting, titles and hyperlinks), and turns keys, pastes and the mouse into the bytes a program expects.

18 properties · 5 events · 20 methods · 5 parts

Example

A pretend shell runs in the page: try help, build, colours or clear. In your application, connect write() and bmxData to your own server instead.

Show markup
<bmx-terminal id="ex-term" label="Build console" style="--bmx-terminal-height: 18rem"></bmx-terminal>
<p class="note" style="margin: 12px 0 0">A pretend shell runs in the page: try <code>help</code>, <code>build</code>, <code>colours</code> or <code>clear</code>. In your application, connect <code>write()</code> and <code>bmxData</code> to your own server instead.</p>

<script type="module">
  await customElements.whenDefined('bmx-terminal');
  const term = document.getElementById('ex-term');
  const E = '\x1b';
  const prompt = () => term.write(`${E}[1;32mdemo@bmx${E}[0m:${E}[1;34m~${E}[0m$ `);
  const commands = {
    help: () => term.write('Commands: help, build, colours, clear\r\n'),
    clear: () => term.write(`${E}[2J${E}[3J${E}[H`),
    colours: async () => {
      let s = '';
      for (let i = 0; i < 16; i += 1) s += `${E}[48;5;${i}m  `;
      s += `${E}[0m\r\n`;
      for (let i = 0; i < 48; i += 1) s += `${E}[48;2;${i * 5};${80 + i * 3};${240 - i * 4}m `;
      await term.write(`${s}${E}[0m\r\n`);
    },
    build: async () => {
      await term.write(`${E}]0;Building...${E}\\${E}[36mcompiling${E}[0m 412 modules\r\n`);
      for (let p = 0; p <= 100; p += 20) {
        await term.write(`\r${E}[K[${'#'.repeat(p / 5).padEnd(20, '.')}] ${p}%`);
        await new Promise(r => setTimeout(r, 120));
      }
      await term.write(`\r\n${E}[32m✓${E}[0m built in 0.6 s - see https://docs.binarymission.co.uk\r\n${E}]0;Build console${E}\\`);
    },
  };
  let line = '';
  term.addEventListener('bmxData', async event => {
    for (const ch of event.detail.data) {
      if (ch === '\r') {
        await term.write('\r\n');
        const run = commands[line.trim()];
        if (run) await run();
        else if (line.trim()) await term.write(`${line.trim()}: command not found\r\n`);
        line = '';
        await prompt();
      } else if (ch === '\x7f') {
        if (line) {
          line = line.slice(0, -1);
          await term.write('\b \b');
        }
      } else if (ch === '\x03') {
        line = '';
        await term.write('^C\r\n');
        await prompt();
      } else if (ch >= ' ') {
        line += ch;
        await term.write(ch);
      }
    }
  });
  await term.write(`Welcome to the ${E}[1mBMX terminal${E}[0m. Type ${E}[33mhelp${E}[0m and press Enter.\r\n`);
  await prompt();
</script>

It does not connect to anything. Give it output with write() and send on what bmxData reports - over your own WebSocket, SSH gateway or serial bridge - and tell the other end the size bmxResize reports:

const term = document.querySelector('bmx-terminal');
const socket = new WebSocket('wss://example.com/pty');
socket.onmessage = e => term.write(e.data);
term.addEventListener('bmxData', e => socket.send(e.detail.data));
term.addEventListener('bmxResize', e => socket.send(JSON.stringify({ resize: e.detail })));

Output: only the rows in view are drawn, in runs of like-styled text, and writes are parsed in slices between frames, so a flood of output keeps the page responsive. Scrollback is kept to scrollback lines. Text wraps and re-wraps as the element is resized; it fits its box unless cols and rows are set.

Reading: select with the mouse (Shift held when a program has taken the mouse) and copy with Ctrl+C - which sends Ctrl+C to the program when nothing is selected - or Ctrl+Shift+C. Ctrl+Shift+F searches the output (case, whole word, regular expression). Web addresses and the program's own hyperlinks open with Ctrl+click (or Command+click). Shift+Page Up/Down scroll.

Keyboard: every key goes to the program, Tab included. Ctrl+Shift+M (or tab-moves-focus) lets Tab leave the terminal instead, so it never traps keyboard focus. Input methods for Chinese, Japanese and Korean work. screen-reader-mode reads new output aloud as it arrives.

Properties

PropertyAttributeTypeDefaultDescription
appearance appearance 'dark' | 'light' 'dark' The colour scheme: dark or light. Every colour is a custom property and can be themed.
bell bell 'visual' | 'none' 'visual' What a bell (BEL) does: flash the terminal, or nothing. bmxBell fires either way.
cols cols number — Fixed columns. Default: as many as fit the element's width.
convertEol convert-eol boolean false Treat \n as a new line (carriage return and line feed), for plain text that is not from a terminal program.
copyOnSelect copy-on-select boolean false Copy the selection as soon as it is made.
cursorBlink cursor-blink boolean true Blink the cursor (never with reduced motion).
cursorStyle cursor-style BmxTerminalCursorShape 'block' The cursor's shape, until a program asks for another.
fontSize font-size number 14 Text size in pixels. Two-way (the toolbar's larger and smaller buttons change it).
hideLabel hide-label boolean true Hide the label visually while keeping it as the accessible name.
label label string — The terminal's name for assistive technology, and the toolbar's title until a program sets one.
lineHeight line-height number 1.25 Line height as a multiple of the text size.
readonly readonly boolean false Show output only; nothing typed is sent.
rows rows number — Fixed rows. Default: as many as fit the element's height (--bmx-terminal-height).
screenReaderMode screen-reader-mode boolean false Read new output aloud as it arrives (throttled).
scrollback scrollback number 5000 Lines kept above the screen.
strings strings Partial<BmxTerminalStrings> | string — Words to show instead of the English ones, as an object or JSON.
tabMovesFocus tab-moves-focus boolean false Let Tab and Shift+Tab move focus out of the terminal instead of going to the program. Two-way (Ctrl+Shift+M).
toolbar toolbar boolean true Show the toolbar (title, search, copy, paste, clear, text size).

Events

EventDetailDescription
bmxBell void A program rang the bell.
bmxData BmxTerminalDataDetail Input for the program: keys, pastes, mouse and focus reports, and replies to its requests.
bmxLinkActivate BmxTerminalLinkDetail A link is being opened. Cancel it to open it yourself, or not at all.
bmxResize BmxTerminalResizeDetail The size in cells changed: tell the program (a pseudo-terminal resize).
bmxTitleChange { readonly title: string; } A program set the window title.

Methods

MethodSignatureDescription
clear clear() => Promise<void> Clears the screen and the scrollback, keeping the line the cursor is on.
clearSelection clearSelection() => Promise<void> Clears the selection.
find find(query: string, options?: BmxTerminalSearchOptions) => Promise<number> Searches the output and selects the first match from the bottom; returns how many there are.
findNext findNext() => Promise<boolean>
findPrevious findPrevious() => Promise<boolean>
fit fit() => Promise<BmxTerminalResizeDetail> Fits the columns and rows to the element's box now; returns the size.
getDimensions getDimensions() => Promise<BmxTerminalResizeDetail & { cellWidth: number; cellHeight: number; }> The size in cells.
getModes getModes() => Promise<BmxTerminalModes> The modes the program has set: cursor keys, bracketed paste, mouse reporting, the alternate screen.
getSelection getSelection() => Promise<string> The selected text, or ''.
getText getText(options?: { screenOnly?: boolean; }) => Promise<string> The output as plain text, wrapped lines joined: the scrollback and the screen, or the screen alone.
paste paste(text: string) => Promise<void> Sends text as if it were pasted (bracketed when the program asked for that).
reset reset() => Promise<void> Back to how it started: screen, scrollback, modes and colours.
resize resize(cols: number, rows: number) => Promise<void> Sets a fixed size in cells: the same as setting cols and rows.
scrollLines scrollLines(n: number) => Promise<void> Scrolls by n lines (negative is up).
scrollToBottom scrollToBottom() => Promise<void>
scrollToTop scrollToTop() => Promise<void>
selectAll selectAll() => Promise<void> Selects everything drawn.
setFocus setFocus() => Promise<void> Moves focus to the terminal.
write write(data: string | Uint8Array | ArrayBuffer) => Promise<void> Output from the program: text, or UTF-8 bytes (decoded as a stream, so a character split across writes is kept whole).
writeln writeln(text?: string) => Promise<void> Writes a line of text and a new line.

Slots

SlotDescription
label Rich label content, in place of the label property.

CSS shadow parts

PartDescription
cursor The cursor.
label The label.
search The search bar.
toolbar The bar above the output.
viewport The scrolling output.

CSS custom properties

PropertyDescription
--bmx-terminal-ansi-0 Black (and 1 red, 2 green, 3 yellow, 4 blue, 5 magenta, 6 cyan, 7 white; 8 to 15 the bright ones).
--bmx-terminal-background Behind the text.
--bmx-terminal-cursor The cursor's colour.
--bmx-terminal-font-family The typeface; a fixed-width one.
--bmx-terminal-foreground The default text colour.
--bmx-terminal-height The output's height, when rows is not set.
--bmx-terminal-match A search match's background.
--bmx-terminal-match-current The current search match's background.
--bmx-terminal-padding Space around the text.
--bmx-terminal-radius Corner radius of the frame.
--bmx-terminal-selection The selection's background.
--bmx-terminal-toolbar-background The toolbar and search bar.