How do i make a text button or smt in terminal using python. like a gui but completly in text not using downloadble liberys

i wanted to do this but they said i need textual and i really dont like how it looks.

i want something like

> start
settings
exit

and being navigatble like

start

> settings
exit

Sounds like what you want is curses.

Try going through a tutorial and see whether it suits your purposes.

i saw something about like clearing the terminal and doing the printing again for it to like change the button. is that in the thing that you sent?

Yep. You’ll end up with some sort of “repaint, wait for input, act on that input” loop.

im going to read it and get back to you

is there any other tutorials for it? this one is kinda outdated i tested the example codes and its not working

i think his gone. is there anyone else with any soulotions?

i think his gone.

This is a web forum for asynchronous discussion, sometimes it takes
hours or even days for people to respond to your posts.

is there anyone else with any soulotions?

All the available solutions I’m aware of are in the same vein. Keep
in mind that “terminals” aren’t a standardized technology, so making
software portable even just to a variety of commonly-used terminals
(or more properly “terminal emulators” in this century) requires
quite a lot of code. If you want to be able to do this purely with
modules from CPython’s standard library then it’s going to be a lot
of work essentially reimplementing the “downloadable libraries” you
seem to insist on avoiding.

If that’s truly your goal, then you’re better off looking at the
source code for libs like Urwid to understand how they drive at
least the specific terminal emulators you want to support. But
there’s a reason basically everyone who writes “TUI” applications
relies on one of these popular libraries rather than recreating it
all on their own from scratch.

i know but there were some things like idk c64 or smt. that when you executed the terminal would get cleared. so i was thinking is it possible that like
if a person clicks arrow down. → clear terminal. print those things again but this time put the selected box around the settings tab or smt

You want a program in the terminal that people can navigate with the mouse? That seems ambitious. The standard loop that’s going to work reliably is “wait for keyboard input” → “parse keyboard input” → “redraw whole terminal”. I think you can get that to work relatively easily and reliably.

Not impossible. The curses module (and the underlying ncurses library) does support mouse inputs. The OP will do well to take a tutorial on using curses with Python.

not mouse inputs. wait for keyboard input i want. but idk how to get it to work

For an example of a simple TUI, that relies only on the standardlib:

import sys
import termios
import tty
from dataclasses import dataclass, replace
from typing import Protocol


COLORS: dict[str, str] = {
    "black": "30",
    "red": "31",
    "green": "32",
    "yellow": "33",
    "blue": "34",
    "magenta": "35",
    "cyan": "36",
    "white": "37",
}


# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class Settings:
    bold: bool = False
    italic: bool = False
    color: str = "white"


# ---------------------------------------------------------------------------
# Actions
# ---------------------------------------------------------------------------


class Action(Protocol):
    def apply(self, settings: Settings) -> Settings:
        ...


@dataclass(frozen=True)
class ToggleBold:
    def apply(self, settings: Settings) -> Settings:
        return replace(
            settings,
            bold=not settings.bold,
        )


@dataclass(frozen=True)
class ToggleItalic:
    def apply(self, settings: Settings) -> Settings:
        return replace(
            settings,
            italic=not settings.italic,
        )


@dataclass(frozen=True)
class SetColor:
    color: str

    def apply(self, settings: Settings) -> Settings:
        return replace(
            settings,
            color=self.color,
        )


# ---------------------------------------------------------------------------
# Cursors
# ---------------------------------------------------------------------------


class Cursor(Protocol):
    def process_key(self, key: str) -> CursorResult:
        ...


@dataclass(frozen=True)
class CursorResult:
    cursor: Cursor
    action: Action | None = None
    message: str | None = None


@dataclass(frozen=True)
class BoldCursor:
    def process_key(self, key: str) -> CursorResult:
        if key == "\x1b[A":  # Up
            return CursorResult(ColorCursor())

        if key == "\x1b[B":  # Down
            return CursorResult(ItalicCursor())

        if key in ("\r", "\n"):
            return CursorResult(
                cursor=self,
                action=ToggleBold(),
            )

        return CursorResult(self)


@dataclass(frozen=True)
class ItalicCursor:
    def process_key(self, key: str) -> CursorResult:
        if key == "\x1b[A":
            return CursorResult(BoldCursor())

        if key == "\x1b[B":
            return CursorResult(ColorCursor())

        if key in ("\r", "\n"):
            return CursorResult(
                cursor=self,
                action=ToggleItalic(),
            )

        return CursorResult(self)


@dataclass(frozen=True)
class ColorCursor:
    text: str = ""

    def process_key(self, key: str) -> CursorResult:
        if key == "\x1b[A":
            return CursorResult(ItalicCursor())

        if key == "\x1b[B":
            return CursorResult(BoldCursor())

        if key in ("\r", "\n"):
            if self.text in COLORS:
                return CursorResult(
                    cursor=ColorCursor(),
                    action=SetColor(self.text),
                )

            return CursorResult(
                cursor=ColorCursor(),
                message=f"Invalid color: {self.text!r}",
            )

        if key in ("\x7f", "\b"):
            return CursorResult(
                ColorCursor(self.text[:-1]),
            )

        if key.isprintable():
            return CursorResult(
                ColorCursor(self.text + key.lower()),
            )

        return CursorResult(self)


# ---------------------------------------------------------------------------
# Global state
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class State:
    settings: Settings
    cursor: Cursor
    message: str | None = None

    def process_key(self, key: str) -> State:
        result = self.cursor.process_key(key)

        settings = self.settings
        if result.action is not None:
            settings = result.action.apply(settings)

        return State(
            settings=settings,
            cursor=result.cursor,
            message=result.message,
        )


# ---------------------------------------------------------------------------
# Terminal I/O
# ---------------------------------------------------------------------------


def read_key() -> str:
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)

    try:
        tty.setraw(fd)
        key = sys.stdin.read(1)

        if key == "\x1b":
            key += sys.stdin.read(2)

        return key
    finally:
        termios.tcsetattr(
            fd,
            termios.TCSADRAIN,
            old_settings,
        )


def clear_screen() -> None:
    print("\033[2J\033[H", end="")


def style_code(settings: Settings) -> str:
    codes: list[str] = []

    if settings.bold:
        codes.append("1")

    if settings.italic:
        codes.append("3")

    codes.append(COLORS[settings.color])

    return f"\033[{';'.join(codes)}m"


def draw(state: State) -> None:
    clear_screen()

    print(style_code(state.settings), end="")

    print(
        f"{'>' if isinstance(state.cursor, BoldCursor) else ' '}"
        "toggle bold"
    )

    print(
        f"{'>' if isinstance(state.cursor, ItalicCursor) else ' '}"
        "toggle italic"
    )

    if isinstance(state.cursor, ColorCursor):
        print(f">select color: {state.cursor.text}%")
    else:
        print(" select color")

    if state.message is not None:
        print()
        print(state.message)

    print()
    print("↑/↓: move   Enter: select   q: quit")

    print("\033[0m", end="", flush=True)


# ---------------------------------------------------------------------------
# Main loop
# ---------------------------------------------------------------------------


def main() -> None:
    state = State(
        settings=Settings(),
        cursor=BoldCursor(),
    )

    while True:
        draw(state)

        key = read_key()

        # q is ordinary text while typing a color.
        if key == "q" and not isinstance(state.cursor, ColorCursor):
            break

        state = state.process_key(key)

    clear_screen()


if __name__ == "__main__":
    main()

Or if you prefer a more ‘simple’ implementation:

import sys
import termios
import tty


COLORS: dict[str, str] = {
    "black": "30",
    "red": "31",
    "green": "32",
    "yellow": "33",
    "blue": "34",
    "magenta": "35",
    "cyan": "36",
    "white": "37",
}


def read_key() -> str:
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)

    try:
        tty.setraw(fd)
        key = sys.stdin.read(1)

        if key == "\x1b":
            key += sys.stdin.read(2)

        return key
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)


def clear_screen() -> None:
    print("\033[2J\033[H", end="")


def style_code(*, bold: bool, italic: bool, color: str) -> str:
    codes: list[str] = []

    if bold:
        codes.append("1")
    if italic:
        codes.append("3")

    codes.append(COLORS[color])

    return f"\033[{';'.join(codes)}m"


def main() -> None:
    options = [
        "toggle bold",
        "toggle italic",
        "select color",
    ]

    selected = 0
    bold = False
    italic = False
    color = "white"

    color_input = ""
    message = ""

    while True:
        # -------------------------
        # REDRAW WHOLE SCREEN
        # -------------------------
        clear_screen()

        style = style_code(
            bold=bold,
            italic=italic,
            color=color,
        )
        reset = "\033[0m"

        # Everything from here is displayed in the selected style.
        print(style, end="")

        for index, option in enumerate(options):
            marker = ">" if index == selected else " "

            if index == 2 and selected == 2:
                print(f"{marker}{option}: {color_input}", end="")
                # A simple visible cursor.
                print("%")
            else:
                print(f"{marker}{option}")

        print()
        print(f"current color: {color}")

        if message:
            print(message)

        print()
        print("↑/↓: move   Enter: select   q: quit")

        print(reset, end="", flush=True)

        # -------------------------
        # WAIT FOR KEYBOARD INPUT
        # -------------------------
        key = read_key()

        # -------------------------
        # PROCESS INPUT
        # -------------------------
        message = ""

        if key == "q" and not (selected == 2 and color_input):
            break

        if key == "\x1b[A":  # Up
            color_input = ""
            selected = (selected - 1) % len(options)

        elif key == "\x1b[B":  # Down
            color_input = ""
            selected = (selected + 1) % len(options)

        elif key in ("\r", "\n"):
            if selected == 0:
                bold = not bold

            elif selected == 1:
                italic = not italic

            elif selected == 2:
                if color_input in COLORS:
                    color = color_input
                    color_input = ""
                else:
                    message = f"Invalid color: {color_input!r}"
                    color_input = ""

        elif selected == 2:
            if key in ("\x7f", "\b"):  # Backspace
                color_input = color_input[:-1]

            elif key.isprintable():
                color_input += key.lower()

    clear_screen()


if __name__ == "__main__":
    main()

so i dont have termios. and i tried installing it manny times with windows-curses and stuff and it did not working. is there a way to do it without that?. like i dont want screens. i just want the texts in my terminal you know. like a main menu in a video game

Right, it’s windows causing problems again. Termios is included in most Python 3.14 builds, but not Windows.

I’d recommend installing WSL, and running all your programming projects from WSL. WSL is a linux-emulator that runs on Windows, and is actively being developed by Microsoft (and is for that reason probably not actively being sabotaged by Microsoft :upside_down_face:). And it allows you to get on with programming without having to deal with Windows BS.

But I understand other people have different perspectives.[1]

Once you’ve got WSL installed, you never need to deal with stuff like windows-curses again.


  1. Instead of getting vague guides about which buttons to press and which websites to go to, guides in the linux system can consist of “run these lines of code in your terminal”. This does have it’s own downsides, primarily to do with figuring out what to trust. ↩︎

If it concerns you that you wouldn’t be able to distribute your application to windows users: that is why you should use a downloadable library. They have already solved the (significant) cross-platform compatibility issues.

is there any other things than termios?

i installed arch linux once. and i asked chatgpt how to change my dns it give me the wrong answer it messed up my internet and stuff so i had to switch back to windows

Yes, you can ask ChatGPT to translate the program I gave to use curses instead[1], but then you’ve got the same problem that curses is not included in the windows build of Python by default.

Sorry you had bad experience with arch linux. It’s predictable to me that it would give you trouble, but perhaps you couldn’t have known. Ubuntu, Fedora and Linux Mint are some of the linux distros that are relatively ‘friendly’. Arch Linux is not.

WSL is something else again: Install WSL | Microsoft Learn . I highly recommend giving it a try.


  1. or at least I can ↩︎

Or you can try MSYS2, which includes a Cygwin-based Python with ncurses. Cygwin is a POSIX compatibility layer for Windows, so you don’t need to install Linux.

You might want to look in to Textual. Works on Mac, Windows, and Linux.