Skip to content
A non-opinionated TABBED menu system to control program flow interactively for terminal-based programs
Branch: master
Clone or download
Latest commit d0f19f0 Apr 27, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
appveyor Change Write-Host to Write-Output for codacy Apr 7, 2019
docs Fix docs Apr 26, 2019
example_app Renamed repo/project to pytabby Apr 25, 2019
example_configs Change, add and move example configs Apr 25, 2019
src/pytabby Renamed repo/project to pytabby Apr 25, 2019
tests Renamed repo/project to pytabby Apr 25, 2019
.gitignore Bugfixes minor Apr 25, 2019
.readthedocs.yml Update rtd.yml Apr 25, 2019
.travis.yml Rejig travis, requirements again, correcting spelling of package too Apr 25, 2019
CHANGELOG.rst Finish docs Apr 25, 2019
LICENSE Initial commit Mar 24, 2019
README.rst Add blog link Apr 27, 2019
appveyor.yml Replace accidentally deleted requirements Apr 25, 2019
prerelease_checklist.txt Renamed repo/project to pytabby Apr 25, 2019
pyproject.toml Rejig configs Apr 25, 2019
requirements-ci.txt Add pygments, apparently Apr 25, 2019
requirements-dev.txt rejig requirements Apr 25, 2019
requirements.txt Renamed repo/project to pytabby Apr 25, 2019
setup.cfg Ready for PyPI Apr 25, 2019 Fix download_url Apr 25, 2019
tox.ini Renamed repo/project to pytabby Apr 25, 2019



A flexible, non-opinionated, tabbed menu system to interactively control program flow for terminal-based programs. It's a class with one sole public method which runs in a while loop as you switch tabs (if you want tabs, that is; you're free not to have any) or if you enter invalid input, and then returns a string based on the value you selected that you can use to control the outer program flow.

Of course, you can run the class itself in a while loop in the enclosing program, getting menu choice after menu choice returned as you navigate a program.

Blog post about why I did this.


pip install pytabby



On readthedocs.


from pytabby import Menu
myconfig = Menu.safe_read_yaml('path/to/yaml')
# or Menu.read_json() or just pass a dict in the next step
mymenu = Menu(myconfig)
result =

if result == 'result1':
elif result == 'result2':
# etc...

See it in action!


*Why did you make this?
Well, it was one of those typical GitHub/PyPI scenarios, I wanted a specific thing, so I made a specific thing and then I took >10X the time making it a project so that others can use the thing; maybe some people will find it useful, maybe not. I like running programs in the terminal, and this allowed me to put a bunch of utilities like duplicate file finders and bulk file renamers all under one umbrella. If you prefer GUIs, there are plenty of simple wrappers out there,
Why can't I return handlers?
Out of scope for this project at this time, but it's on the Wish List. For now, the Menu instance just returns strings which the outer closure can then use to control program flow, including defining handlers using control flow/if statement based on the string returned by
Why are my return values coming in/out strings?
To keep things simple, all input and output (return) values are converted to string. So if you have config['tabs'][0]['items][0]['item_returns'] = 1, the return value will be '1'.
Why do 'items' have both 'item_choice_displayed' and 'item_inputs' keys?
To keep things flexible, you don't have to display exactly what you'll accept as input. For example, you could display 'yes/no' as the suggested answers to a yes or no question, but actually accept ['y', 'n', 'yes', 'no'], etc.
I have 'case_sensitive' = False, but my return value is still uppercase.
case_sensitive only affects inputs, not outputs
What's up with passing a dict with the tab name as a message to
The message might be different depending on the tab, and run() only exits when it returns a value when given a valid item input. It changes tabs in a loop, keeping that implementation detail abstracted away from the user, as is right.


  • PyYAML>=5.1
  • schema>=0.7.0

Wish List:

  • a way to dynamically silence ("grey out", if this were a GUI menu system) certain menu items, which may be desired during program flow, probably by passing a list of silenced tab names and return values
  • have an option to accept single keypresses instead of multiple keys and ENTER with the input() function, using msvcrt package in Windows or tty and termios in Mac/Linux. (This will make coverage platform- dependent, so it will have to be cumulative on travis and appveyor)

Here's a picture of the pytabby tabby!

Photo credit: Erik-Jan Leusink via Unsplash

You can’t perform that action at this time.