DEV Community

Cover image for Building web-based terminal components with Termino.js
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

Building web-based terminal components with Termino.js

Written by Chibuike Nwachukwu✏️

Terminals are a way to access applications using a set of predefined commands. They can be for a variety of use cases, such as connecting to a remote server, interacting with a database engine, and much more. Termino.js is a web-based terminal that makes it easy to embed a terminal into a web application.

This article will explore what Termino.js is, and how to easily integrate it into your web app to create great terminals.

Let’s get started!

Introduction to web-based terminals

A web-based terminal is a terminal that can be accessed on a web browser. It is embedded in a browser and allows users to interact with applications, systems, remote servers and databases, and much more without needing a physical terminal setup or installation on the user's device. With web-based terminals, everything users need is right in the browser.

Understanding Termino.js and its use cases

Termino.js is an open source JavaScript library that easily integrates an intuitive, web-based terminal into your webpage. Extendable and customizable, Termino.js is useful if you want to display a component on your site that could act as a terminal without the overhead of importing a fully blown terminal. It also saves time by quickly integrating a custom, lightweight terminal in your web app.

Termino.js can be used:

  • To connect to a third-party SSH server
  • As custom shells or terminals for web applications
  • To create text-based interactive games
  • To create interactive tutorials

In our demo project, we will see how to receive inputs from an end user and return customized outputs. We will also explore how to customize the look and feel of the terminal and later see an advanced section where we will create custom methods that various user commands can interact with.

Setting up the project

To start, we’ll create a simple web-based application. Head over to the terminal and run the following command:

mkdir termino-web && cd termino-web
Enter fullscreen mode Exit fullscreen mode

Next, create an index.html file:

touch index.html
Enter fullscreen mode Exit fullscreen mode

Update the contents as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="terminal">
      <pre></pre> <textarea class="termino-input" rows="1" wrap="hard"></textarea> </div> <script src="index.mjs" type="module"></script> </body> </html>```



Next, create the `index.mjs` file. This file will contain the logic for the app and is where Termino.js will be initiated and called:



```javascript
touch index.mjs
Enter fullscreen mode Exit fullscreen mode

Update the contents of the file with the following code:

import { Termino } from "https://cdn.jsdelivr.net/gh/MarketingPipeline/Termino.js@v1.0.0/dist/termino.min.js";
let term = Termino(document.getElementById("terminal"));
term.output("Hello world!");
Enter fullscreen mode Exit fullscreen mode

When we visit the page, we will see the interactive terminal in action: Interactive Terminal Demo We just implemented a web terminal that takes user input and returns predefined output with just three lines of code. And because Termino.js is lightweight, this doesn't have any negative impact on the webpage — more reason for choosing this terminal!

Customizing Termino.js

To explore more of Termino.js’ functionalities, let’s now customize the basic app we created.

First, create an index.css file:

touch index.css
Enter fullscreen mode Exit fullscreen mode

Then, replace the contents with the following:

.repl {
  text-shadow: none;
  color: #333;
  background: #f8f8f8;
  padding: 0;
  text-align: left;
  width: 600px;
  margin: 50px auto;
  border-radius: 3px;
  border: 1px solid #ddd;
  overflow: hidden;
}
.repl code {
  height: 200px;
  overflow-y: scroll;
}
pre {
  margin: 0;
}
.termino-console {
  padding: 11px 16px;
  display: block;
}
.termino-input-container {
  display: flex;
}
.termino-input-container > * {
  outline: none;
  border: none;
  white-space: pre-wrap;
  font-family: monospace;
  color: #444;
  background: #f0f0f0;
  min-height: 14px; /* minimum one line */
  padding: 10px;
  margin: 0;
  border-radius: 0 0 3px 3px;
  border-top: 1px solid #ddd;
}
.termino-input {
  flex: 1;
  height: 100%; /* start off one line tall */
  padding-left: 0;
}
.termino-prompt a {
  font-weight: bold;
  padding: 8px 10px;
}
@media screen and (max-width: 800px) {
  .repl {
    width: 100%;
  }
}
.lua {
  resize: none;
  overflow: hidden;
}
pre {
  word-break: break-all;
  white-space: pre-line;
}
.repl code {
  scroll-behavior: smooth;
}
/* hide scrollbar but allow scrolling */
.repl code {
  -ms-overflow-style: none; /* for Internet Explorer, Edge */
  scrollbar-width: none; /* for Firefox */
  overflow-y: scroll;
}
.repl code::-webkit-scrollbar {
  display: none; /* for Chrome, Safari, and Opera */
}
.loading:after {
  content: " .";
  animation: dots 1s steps(5, end) infinite;
}
@keyframes dots {
  0%,
  20% {
    color: rgba(0, 0, 0, 0);
    text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
  }
  40% {
    color: white;
    text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
  }
  60% {
    text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
  }
  80%,
  100% {
    text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, update the index.html file, adding the link to the CSS stylesheet and any necessary changes:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <section
      class="section--standalone"
      id="terminal"
      terminal-processed="true"
    >
      <div class="repl">
        <pre></pre> <div class="termino-input-container"> <label id="termino-prompt" for="termino-input"></label> <textarea class="termino-input" rows="1" wrap="hard" placeholder="Enter input here!" spellcheck="false" ></textarea> </div> </div> </section> <script src="index.mjs" type="module"></script> </body> </html>
Enter fullscreen mode Exit fullscreen mode

Now, when we visit the page, we will see the interactive terminal in action: Terminal Demo With Styles In the above example, we applied custom styles to Termino.js. This is useful especially if you want to conform to a specific design pattern or branding style.

Advanced use cases for Termino.js

Now, let's explore some more complex uses of Termino.js.

We will see how to add custom methods that get called as a user interacts with the terminal. This is a great way of showing how easy it is to interact with custom methods within your web app, like interacting with a third-party endpoint via an API call.

Update the contents of the index.mjs file with the code below:

import { Termino } from "https://cdn.jsdelivr.net/gh/MarketingPipeline/Termino.js@v1.0.0/dist/termino.min.js";
let term = Termino(document.getElementById("terminal"));
function print_termino() {
  term.output("Termino.js");
}
async function terminalApp() {
  term.output(`1\. Print Termino.js
    2\. Multiply two numbers
    3\. Who created you?
    4\. Exit`);
  // call Termino.js / your terminal for inital input
  let termvalue = await term.input("What would you like to do?");
  // function to multiply numbers
  async function multiply_numbers() {
    let number1 = await term.input("First number to multiply");
    let number2 = await term.input("Second number to multiply");
    term.output(
      `Product of ${number1} and ${number2} is ${
        Number(number1) * Number(number2)
      }`
    );
  }
  async function printDev() {
    term.output(
      "Chibuike is the creator of this demo. His Github profile is <a href='https://github.com/chyke007'> chyke007</a>, if you would like to hire him for any work. Feel free to reach him on GitHub"
    );
  }
  if (termvalue === "1") {
    await print_termino();
  }
  if (termvalue === "2") {
    await multiply_numbers();
  }
  if (termvalue === "3") {
    await printDev();
  }
  if (termvalue === "4") {
    term.output("You chose option 4, exiting terminal");
    await term.delay(2000);
    term.kill();
  }
  if (
    termvalue != "1" &&
    termvalue != "2" &&
    termvalue != "3" &&
    termvalue != "4"
  ) {
    term.output("Invalid choice");
  }
  // after called - repeat function again (if not exit menu)
  if (termvalue != "4") {
    terminalApp();
  }
}
terminalApp();
Enter fullscreen mode Exit fullscreen mode

In this code snippet, the user is prompted to enter an input that is between 1 and 4. Depending on the input, different actions are carried out. If an invalid input is entered by the user, the text Invalid choice is shown by the terminal, which you can see on line 48.

After processing each command, the terminalApp method is called to start the process all over again.

A demo of the result is shown below:

Terminal Processing Commands

Benefits of Termino.js

Termino.js offers users many benefits, including the following:

  • Highly customizable: As easy as applying HTML elements and CSS styles to elements
  • No third parties needed: Doesn't require many dependencies; a simple import of the minimized distributed version is all that is required to get started
  • Highly extendable: You can add your commands to make it tailored to a specific use case
  • Supports multiple instances: Can have more than one terminal on the page
  • Lightweight: Very fast, as it relies solely on JavaScript

Potential issues with web-based terminals and terminal components

Despite its many benefits, Termino.js and web-based terminals and terminal components can encounter issues, including:

  • Security: Due to their nature of running on the web using imported scripts, web-based terminals are vulnerable to exploits if not implemented well or if user input isn't properly sanitized. Issues like cross-site scripting or injection attacks can occur
  • Browser compatibility: Most browsers may render the component differently leading to inconsistent user experience
  • Performance: Web-based terminals may run into performance issues if data increases significantly or there are many complex operations. This is because it runs on a browser that has limits and allocations. This could result in sluggishness or unresponsiveness of the web application
  • Session persistence: Web-based terminals may not preserve the session state between page reloads. Users may end up losing progress when this happens or if a browser tab is accidentally closed
  • User experience: The user experience of web-based terminals may differ from native terminals. Some users may appreciate the accessibility and convenience of web-based terminals, while others may insist on having the familiarity of native terminals

Frequently asked questions about web-based terminals

How do I create a web-based terminal?

Most web-based terminals are included as external script tags on the webpage. Once that is done, simple HTML content can be used to design the interface and have it all running quickly.

Can I have multiple terminals on a page?

Yes, most terminals support this. It usually depends on the terminal in use. Termino.js supports having multiple terminals on the same page.

Can I include my custom logic?

Yes, you can — most web-based terminals are extendable, including Termino.js.

Does it have an impact on my website performance?

No, a web-based terminal is lightweight and mostly loaded from a CDN, so it won’t hurt the performance of your web app.

Termino.js alternatives

Below are other open source web-based terminals that compare with Termino.js:

  • Xterm.js: Written in Typescript and is very fast. It supports a GPU-accelerated renderer and requires no dependencies to get started. Supports emojis and is built with accessibility in mind. It also supports customization, and integration with existing terminal apps, like Vim and bash, and has great documentation and a large developer community for support
  • Butterfly: An Xterm.js-compatible terminal that supports most of its functions. Supports multiple sessions and desktop notifications on terminal output. It is highly customizable, supporting theming in CSS/SASS and HTML in the terminal, and also supports X509 certificates for security
  • Web Terminal: This is used for InterSystems products, enabling access to databases from everywhere. It has inbuilt syntax highlighting and autocompletion. Commonly used commands can also be saved to favorites. It mainly focuses on helping users manage their InterSystems products easily via remote access.
  • jQuery Terminal Emulator: Extends jQuery as a plugin. It has inbuilt keyboard shortcuts and also allows the creation of custom ones. Easily customizable and supports animations. Supports multiple terminals on a page, with each having the option of different commands and command history (using local storage)

Conclusion

Termino.js is a powerful and flexible library for integrating web-based terminals into applications. This tutorial demonstrated the simplicity of setting up a basic terminal using Termino.js and provided an exploration of its more advanced functionalities, such as custom styling.

I hope you enjoyed this article and have learned how easy it is to create a web-based terminal in your next project. Thanks for reading!


Are you adding new JS libraries to build new features or improve performance? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

LogRocket Signup

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — start monitoring for free.

Top comments (0)