# Simple Menu Navigation and selection

**URL:** <https://ziggit.dev/t/simple-menu-navigation-and-selection/12602>\
**Category:** Help\
**Tags:** language, io\
**Created:** [October 19, 2025, 3:27pm UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602 "2025-10-19T15:27:04Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![elbci](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/839c29/32.png) [@elbci](https://ziggit.dev/u/elbci)\
**Post date:** [October 19, 2025, 3:27pm UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/1 "2025-10-19T15:27:05Z")

</div>

Hi all, my first post, be gentle.  
I want to print on terminal (linux, if relevant) starting at col x, row y a menu:  
a  
b  
c  
Navigate that menu with Up/Down arrow keys and upon selecting with Enter, print “You selected {}”, selection.

My zig version: 0.15.1

PS

1. I’m aware this will require entering raw mode in terminal but more than that is beyond me and so is understanding the documentation of [Libvaxis: A modern TUI library](https://ziggit.dev/t/libvaxis-a-modern-tui-library/4380) to understand if that is really what I need.

2. It took me just 8 months to exit VIM and recently I managed to write Hello Rust but that was AI assisted, LLMs aren’t yet very good with ZIG (unfortunately for novices like me) so… as I said first, be gentle ;))

---

<div class="post-metadata">

**Author:** ![floooh](https://ziggit.dev/user_avatar/ziggit.dev/floooh/32/2989_2.png) [@floooh](https://ziggit.dev/u/floooh)\
**Post date:** [October 19, 2025, 5:18pm UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/3 "2025-10-19T17:18:11Z")

</div>

With looking for “TUIs” you’re generally on the right track, you can also read up on “curses” and “ncurses” which are ancient TUI frameworks. Under the hood it’s all terminal protocol escape sequences which are tricky/ugly to do manually, but aTUI framework generally handles these details for you.

---

<div class="post-metadata">

**Author:** ![elbci](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/839c29/32.png) [@elbci](https://ziggit.dev/u/elbci)\
**Post date:** [October 19, 2025, 5:40pm UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/4 "2025-10-19T17:40:13Z")

</div>

Thanks @flooh  
I managed with Termion a full 4 hand diagram (I’m doing the probably classic “learn codding by doing a bridge game”) and I was about to switch to Crossterm or maybe even Cursive or Ratatui but I had this simple a,b,c example as a starting point about how to import the library and setup the simplest interaction. Without that, the syntax and the concepts involved are way above my head for now…

---

<div class="post-metadata">

**Author:** ![Justus2308](https://ziggit.dev/user_avatar/ziggit.dev/justus2308/32/5305_2.png) [@Justus2308](https://ziggit.dev/u/Justus2308)\
**Post date:** [October 19, 2025, 6:06pm UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/5 "2025-10-19T18:06:38Z")

</div>

Welcome to Ziggit!

You might want to have another look at vaxis, there‘s an example in the repo which sounds close to what you want:

> <https://github.com/rockorager/libvaxis/blob/main/examples/cli.zig>

you can run it like so:

```zig
zig build example -Dexample=cli

```

I‘m currently using vaxis for a project and I find it very pleasant to work with once you get the hang of it.

---

<div class="post-metadata">

**Author:** ![elbci](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/839c29/32.png) [@elbci](https://ziggit.dev/u/elbci)\
**Post date:** [October 20, 2025, 7:01am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/6 "2025-10-20T07:01:48Z")

</div>

Later edit:  
Thanks all, here it is what I did:

I put the example linked by Justus (thanks) in main.zig

I did zig fetch as instructed here: [GitHub - rockorager/libvaxis: a modern tui library written in zig](https://github.com/rockorager/libvaxis)

```zig
elbci@localhost:~/Documents/ZIG/BridgeSeek> zig fetch --save git+https://github.com/rockorager/libvaxis.git
info: resolved to commit 95b167909f8aaec7f9b2315e6a1f64d9a48afe9b

```

I replaced in my build.zig (autogenerated by zig init)

```zig
     const exe = b.addExecutable(.{
         .name = "BridgeSeek",
         .root_module = b.createModule(.{
             // b.createModule defines a new module just like b.addModule but,
             // unlike b.addModule, it does not expose the module to consumers of
             // this package, which is why in this case we don't have to give it a name.
             .root_source_file = b.path("src/main.zig"),
             // Target and optimization levels must be explicitly wired in when
             // defining an executable or library (in the root module), and you
             // can also hardcode a specific target for an executable or library
             // definition if desireable (e.g. firmware for embedded devices).
             .target = target,
             .optimize = optimize,
             // List of modules available for import in source files part of the
             // root module.
             .imports = &.{
                 // Here "BridgeSeek" is the name you will use in your source code to
                 // import this module (e.g. `@import("BridgeSeek")`). The name is
                 // repeated because you are allowed to rename your imports, which
                 // can be extremely useful in case of collisions (which can happen
                 // importing modules from different packages).
                 .{ .name = "BridgeSeek", .module = mod },
             },
         }),
     });

```

with

```zig
    // // NEW CODE //////////////////////////////////
    // Create a module for the executable (for ZLS support)
    const exe_mod = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Add vaxis dependency to the module
    const vaxis = b.dependency("vaxis", .{
        .target = target,
        .optimize = optimize,
    });
    exe_mod.addImport("vaxis", vaxis.module("vaxis"));

    // Create executable using this module
    const exe = b.addExecutable(.{
        .name = "BridgeSeek",
        .root_module = exe_mod,
    });

    // // END NEW CODE /////////////////////////////////////////////

```

Now the code compiles and runs with zig build run.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [October 20, 2025, 8:02am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/7 "2025-10-20T08:02:26Z")

</div>

If the comments are too much visual noise, you can remove them.  
For future reference, `zig init --minimal or -m` will create a minimal project.  
Probably more minimal than you expect, so I’d recommend against it until you are confident with the build system.

For the most part, you can copy and paste what vaxis tells you, excluding things you already have.

The snippet for zls support is AFAIK unnecessary, the only thing zls needs (only necessary if you want it to report compile errors, other errors will show regardless) is a step called `check` that depends on everything you want it to check. see [Build-On-Save - zigtools](https://zigtools.org/zls/guides/build-on-save/) for more information.

---

<div class="post-metadata">

**Author:** ![elbci](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/839c29/32.png) [@elbci](https://ziggit.dev/u/elbci)\
**Post date:** [October 20, 2025, 8:42am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/8 "2025-10-20T08:42:41Z")

</div>

@vulpesx  
Thanks, I bet the comments in build.zig contain usefull info I’m just not ready to absorb yet.  
I just needed to know that it was a matter of replacing const exe, not just adding the new lines.

Now,  
@Justus2308 (or whomever of the 99.9% smarter than me ppl here):

The example compiles and runs, terminal enters raw mode.

but the result is just:

 ![1](https://ziggit.dev/uploads/default/original/2X/4/498b155312cb4d2ade08ef4ee2bbac5508d6f446.png)

it changes on Tab:

 ![2Tab](https://ziggit.dev/uploads/default/original/2X/0/0b854d2846a152e37029404a52446e9753881fba.png)

on ArrowKeys:

 ![3Arrow](https://ziggit.dev/uploads/default/original/2X/5/54cb7596aa4b7c5b6f0852ad10629a3f40e8d1a9.png)  
 ![4](https://ziggit.dev/uploads/default/original/2X/a/aca062aa3992060d55bd488577ba7e0e6a783687.png)

and exits clean on Ctrl+C restoring the terminal proper.

To get to my goal I need a clean menu positioned at a custom col, row on the terminal.  
PS I’m on a vanilla Tumbleweed, KDE Plasma, Konsole terminal emulator.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [October 20, 2025, 8:52am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/9 "2025-10-20T08:52:50Z")

</div>

normal printing/logging will have issues as its fighting vaxis.

the easy solution is to redirect stderr, logging(by default) and `debug.print` print to stderr, via `[cmd] 2> err.txt` you can then look at `err.txt` for the output.

The more robust and flexable option is to override the log function to output somewhere else:

```zig
// the root file of your root module
pub const std_options: std.Options = .{
    .logFn = myLogFn,
};
fn myLogFn(
        comptime message_level: log.Level,
        comptime scope: @TypeOf(.enum_literal),
        comptime format: []const u8,
        args: anytype,
    ) void {
    // do your logging
}

```

---

<div class="post-metadata">

**Author:** ![elbci](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/839c29/32.png) [@elbci](https://ziggit.dev/u/elbci)\
**Post date:** [October 20, 2025, 9:49am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/10 "2025-10-20T09:49:51Z")

</div>

Inspired by @vulpesx I just comment out  
// log.err(“enter”, .{});  
// log.err(“i = {d}, j = {d}, opt = {s}”, .{ i, j, opt });  
at lines 69 and 92 in the example from @Justus2308 : [libvaxis/examples/cli.zig at main · rockorager/libvaxis · GitHub](https://github.com/rockorager/libvaxis/blob/main/examples/cli.zig)

Great progress, the menu appears after Tab, advances to next option on Tab and finally Enter displays the selection. I guess I can figure on my own why Shit+Tab doesn’t work and add some % modulo to circle the options.

Now, the last piece and I’ll stop spamming your generous forum with my incompetence:  
How do I move the menu to col X, row Y on the terminal?

---

<div class="post-metadata">

**Author:** ![Justus2308](https://ziggit.dev/user_avatar/ziggit.dev/justus2308/32/5305_2.png) [@Justus2308](https://ziggit.dev/u/Justus2308)\
**Post date:** [October 20, 2025, 10:41am UTC](https://ziggit.dev/t/simple-menu-navigation-and-selection/12602/11 "2025-10-20T10:41:55Z")

</div>

I _think_ you can just render everything to a `win.child(opts)` of your main window and play with `opts.x_off` and `opts.y_off` until you get what you want. Haven’t tested that though I’m pretty new to vaxis myself.

If you want to do anything more fancy than what you’re doing right now you might want to switch to `vxfw.App` as it handles lots of lower-level details for you and integrates nicely with the prebuilt widgets in the `vxfw` namespace.
