---
slug: "python-keyboard-menu-in-terminal-with-escape-sequence"
title: "Creating a Terminal Menu Controlled by Arrow Keys in Python (Utilizing Escape Sequences)"
description: "Information on creating a terminal menu that can be navigated using arrow keys without using curses.\nThere were many uncertainties, so I wrote this while referencing other sites. Thank you."
url: "https://www.ytyng.com/en/blog/python-keyboard-menu-in-terminal-with-escape-sequence"
publish_date: "2022-07-24T08:46:08Z"
created: "2022-07-24T08:46:08Z"
updated: "2026-02-27T01:06:32.825Z"
categories: []
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20250605/69126ee3310f492a99ff079148615b95.png.webp?width=768"
has_video: true
has_music: true
video_urls: ["https://media.ytyng.net/ytyng-blog/239/featured-video-1.mp4", "https://media.ytyng.net/ytyng-blog/239/featured-video-2.mp4", "https://media.ytyng.net/ytyng-blog/239/featured-video-3.mp4"]
music_urls: ["https://media.ytyng.net/ytyng-blog/239/featured-music-239-1.mp3", "https://media.ytyng.net/ytyng-blog/239/featured-music-239-2.mp3"]
lang: "en"
---

# Creating a Terminal Menu Controlled by Arrow Keys in Python (Utilizing Escape Sequences)

<p>Information on creating a terminal menu that can be operated with arrow keys without using curses.</p>
<p>Since there were many unclear points, I wrote this while referencing other websites. Thanks.</p>
<p>It was a good study on escape sequences.</p>
<p></p>
<h2>CSI</h2>
<p><code>\033[</code> is a frequently appearing escape sequence, known as the <strong>CSI (Control Sequence Introducer)</strong>, used for a wide range of purposes such as cursor movement and color changes.</p>
<p><code><span>\033[</span></code> is the octal notation. In hexadecimal, it is <code>\x1b[</code>, and both represent the same character. (Incidentally, in decimal, it is 27, so <code>chr(27)</code> + <code>[</code> is also the same)</p>
<p>It can also be expressed as <code>ESC + [</code>.</p>
<p></p>
<p>Details about CSI can be found on the English version of Wikipedia. There doesn't seem to be a Japanese version.</p>
<p><a href="https://en.wikipedia.org/wiki/ANSI_escape_code" target="_blank">https://en.wikipedia.org/wiki/ANSI_escape_code</a></p>
<p></p>
<h2>Drawing</h2>
<p>It is necessary to update multiple drawn lines.</p>
<p>After drawing multiple lines, print <code>\033[nA</code> (n is a number representing the number of lines) to move the cursor up and print again.</p>
<p></p>
<h4>Reference Sites</h4>
<p><a href="https://hacknote.jp/archives/51679/" target="_blank">How to Output and Overwrite Multiple Lines in Python</a><br /><br /></p>
<h2>Key Operations</h2>
<p>It is necessary to accept key inputs. <code>input()</code> is not suitable as it requires pressing the Enter key.</p>
<p>You need to use the <code>readchar</code> library or read using <code>tty</code>.</p>
<p>Note that readchar uses tty internally.</p>
<p>However, it did not work in the terminal within PyCharm. It worked without issues in iTerm.</p>
<p></p>
<h4>Reference Sites</h4>
<p><a href="https://emotionexplorer.blog.fc2.com/blog-entry-126.html" target="_blank">Python Console Single Character Input - Emotion Explorer</a></p>
<p><a href="https://qiita.com/tortuepin/items/e6c72f48115f20744ace" target="_blank">Detecting Key Inputs in Python (tty) - Qiita</a></p>
<h2>Color</h2>
<p>Changing the text color makes it easier to see. Use escape sequences to change the color.</p>
<h4>Reference Sites</h4>
<p><a href="https://www.nomuramath.com/kv8wr0mp/" target="_blank">[python] Try Adding Color to Print Statements – Nomura Mathematics Institute</a></p>
<h2>Code</h2>
<pre>#!/usr/bin/env python3<br /><br />import readchar<br /><br />menu = [<br />    'Arcturus',<br />    'Betelgeuse',<br />    'Capella',<br />    'Deneb',<br />    'Eltanin',<br />    'Fomalhaut',<br />    'Gacrux',<br />]<br /><br /><br />class Color:<br />    BLACK = '\033[30m' # (Text) Black<br />    RED = '\033[31m' # (Text) Red<br />    GREEN = '\033[32m' # (Text) Green<br />    YELLOW = '\033[33m' # (Text) Yellow<br />    BLUE = '\033[34m' # (Text) Blue<br />    MAGENTA = '\033[35m' # (Text) Magenta<br />    CYAN = '\033[36m' # (Text) Cyan<br />    WHITE = '\033[37m' # (Text) White<br />    COLOR_DEFAULT = '\033[39m' # Reset text color to default<br />    BOLD = '\033[1m' # Bold text<br />    UNDERLINE = '\033[4m' # Underline text<br />    INVISIBLE = '\033[08m' # Invisible text<br />    REVERCE = '\033[07m' # Reverse text and background color<br />    BG_BLACK = '\033[40m' # (Background) Black<br />    BG_RED = '\033[41m' # (Background) Red<br />    BG_GREEN = '\033[42m' # (Background) Green<br />    BG_YELLOW = '\033[43m' # (Background) Yellow<br />    BG_BLUE = '\033[44m' # (Background) Blue<br />    BG_MAGENTA = '\033[45m' # (Background) Magenta<br />    BG_CYAN = '\033[46m' # (Background) Cyan<br />    BG_WHITE = '\033[47m' # (Background) White<br />    BG_DEFAULT = '\033[49m' # Reset background color to default<br />    RESET = '\033[0m' # Reset all<br /><br /><br />def tint_color(text):<br />   return f'{Color.BG_CYAN}{Color.BLACK}{text}{Color.RESET}'<br /><br /><br />def help_color(text):<br />    return f'{Color.GREEN}{text}{Color.RESET}'<br /><br /><br />def strong_color(text):<br />    return f'{Color.YELLOW}{text}{Color.RESET}'<br /><br /><br />def main():<br />    current = 0<br />    c = ''<br />    while True:<br />        for i, item in enumerate(menu):<br />        if current == i:<br />            print(tint_color(f'&gt; {item}'))<br />        else:<br />            print(f' {item}')<br />        print(help_color(f'c={repr(c)}, current={current}'))<br />        c = readchar.readkey()<br />        if c == 'q':<br />            # Exit with q<br />            break<br />        elif c == '\r':<br />            # Confirm with Enter<br />            print(f'selected:{strong_color(menu[current])}')<br />            break<br />        if c == 'j' or c == '\x1b[B':<br />            # Move cursor with j or &darr;<br />            if current &lt; len(menu) - 1:<br />                current += 1<br />        if c == 'k' or c == '\x1b[A':<br />            # Move cursor with k or &uarr;<br />            if current &gt; 0:<br />                current -= 1<br />        print(f'\033[{len(menu) + 1}A', end='')<br /><br /><br />if __name__ == '__main__':<br />    main()</pre>
<p></p>
