<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Slot | Description |
|---|---|
label |
Rich label content, in place of the label property. |
CSS shadow parts
| Part | Description |
|---|---|
cursor |
The cursor. |
label |
The label. |
search |
The search bar. |
toolbar |
The bar above the output. |
viewport |
The scrolling output. |
CSS custom properties
| Property | Description |
|---|---|
--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. |