Terminal menus that don't make you want to quit. (Unless you press Q.)
PSYamlTUI turns a simple YAML file into a fully navigable terminal UI - recursive submenus, automatic terminal detection, and safe script execution included.
| Build | Tests | Gallery | Downloads | License | PowerShell |
|---|---|---|---|---|---|
- Features
- Installation
- Quick start
- VS Code setup
- Parameter reference
- YAML schema
- Guides
- Navigation
- Themes
- Contributing
- License
- Author
| Feature | What it gives you |
|---|---|
| YAML-driven menus | Define your menu once in YAML and launch it anywhere with a single command. |
| Recursive submenus | Nest as deep as needed via inline children or external import files. |
| Three rendering tiers | ANSI+Unicode, Write-Host+Unicode, and Write-Host+ASCII for modern and legacy terminals. |
| Automatic terminal detection | PSYamlTUI detects terminal capabilities automatically. |
| Five border styles | Choose Single, Double, Rounded, Heavy, or ASCII. |
| Fully customizable themes | Pass a theme hashtable or point -ThemePath to a YAML/JSON theme file. |
| Remappable key bindings | Map navigation keys to match your workflow. |
| Token substitution | Use {{key}} placeholders with values from vars.yaml, -VarsPath, or -Context. |
| Before hooks | Gate branch access or leaf execution with reusable hook functions. |
| Status bar | Show context such as connected user, environment, or any runtime metadata. |
| Safe execution | Calls scripts/functions via & with path validation, root-jail enforcement, and injection checks. |
| PowerShell 5.1 and 7+ | Works on both Windows PowerShell and PowerShell Core. |
PowerShell 5.1 or higher is required. No other dependencies needed → everything is bundled.
# Install from PowerShell Gallery
Install-Module -Name PSYamlTUI -Scope CurrentUser
# Import
Import-Module PSYamlTUI
# Verify
Get-Command -Module PSYamlTUICreate menu.yaml in your current folder:
menu:
title: "Main Menu"
items:
- label: "Show date"
call: "Get-Date"
- label: "Exit"
exit: true$statusData = @{
'Connected As' = ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name -split '\\')[-1]
'Environment' = 'Production'
}
$keyBindings = @{
Up = [System.ConsoleKey]::UpArrow
Down = [System.ConsoleKey]::DownArrow
Select = [System.ConsoleKey]::RightArrow
Back = [System.ConsoleKey]::LeftArrow
Quit = 'X'
Home = 'H'
}Start-Menu -Path .\menu.yaml -BorderStyle Rounded -StatusData $statusData -KeyBindings $keyBindingsThis is optional and intended for testing or demos. It is not a required setup step.
If you cloned the repository and your terminal is at the root of the cloned folder, you can run the example launchers directly with full relative paths:
# From repo root
.\Docs\Examples\MenuLaunchers\Simple-Example.ps1
.\Docs\Examples\MenuLaunchers\Intermediate-Example.ps1
.\Docs\Examples\MenuLaunchers\Advanced-Example.ps1If you create your own launcher in this folder (for example menus.ps1), run it the same way:
.\Docs\Examples\MenuLaunchers\menus.ps1Install the Red Hat YAML extension (redhat.vscode-yaml) from the VS Code marketplace.
Without it the YAML files open as plain text -- no validation, no autocompletion, no hover docs.
With it, the extension reads menu.schema.json at the repo root and gives you:
- Validation -- unknown keys, missing required keys, and wrong value types are flagged inline
- Autocompletion --
Ctrl+Spaceoffers valid properties at any point in a menu or vars file - Hover documentation -- hover over any key to see what it does
The extension is already configured via .vscode/settings.json checked into the repo.
No manual setup is needed beyond installing the extension.
The schema applies to menu files, submenu import files, and vars files:
| File pattern | What it validates |
|---|---|
*.menu.yaml |
Root menu files -- requires menu: wrapper with title: and items: |
**/import-*.yaml |
Submenu import files -- top-level items: only, no menu: wrapper |
vars.yaml / *.vars.yaml |
Vars files -- top-level vars: map, scalar values only |
Theme files (*.theme.yaml) use a flat key/value structure and are not covered by this schema.
.vscode/settings.json sets yaml.format.singleQuote: true so the YAML formatter always
uses single-quoted strings. This is important because YAML processes the two quote styles differently:
| Quote style | Escape processing | Safe for Windows paths? |
|---|---|---|
'single' |
None -- every character is literal | Yes |
"double" |
Yes -- \n, \t, \\, etc. are interpreted |
No -- bare \ can corrupt silently |
Always use single quotes for values in your menu and vars files. Backslashes, curly braces, and other special characters are passed through exactly as written:
# Safe -- single-quoted, backslash is literal
scriptsPath: 'C:\scripts\myapp'
# Unsafe -- double-quoted, \s and \m are undefined escape sequences
scriptsPath: "C:\scripts\myapp"The formatter enforces this automatically on save, so you generally do not need to think about it.
| Parameter | Type | Default | Description |
|---|---|---|---|
-Path |
string | .\menu.yaml |
Root menu YAML path |
-VarsPath |
string | auto .\vars.yaml |
Vars YAML path (vars: map) |
-Context |
hashtable | none | Runtime token values (wins over vars) |
-BorderStyle |
string | Single |
Single, Double, Rounded, Heavy, ASCII |
-KeyBindings |
hashtable | defaults | Key map for Up/Down/Select/Back/Quit/Home |
-Theme |
hashtable | defaults | Theme overrides as ConsoleColor values |
-ThemePath |
string | none | YAML/JSON theme file path |
-StatusData |
hashtable | none | Status bar key/value pairs |
-Timer |
switch | off | Shows elapsed execution time after leaf actions |
- Docs/menu-yaml-reference.md → schema, tokens, imports, hooks, and examples
- Docs/vars-vs-context.md → when to use vars.yaml vs runtime context values
- Docs/hook-function-best-practices.md → how to write reliable hook functions
- Docs/root-jail-security.md → how script path security and root-jail enforcement work
- Docs/Examples/README.md → runnable simple, intermediate, and advanced examples
PSYamlTUI infers the node type from the keys present -- you never have to declare it explicitly.
menu:
title: "Main Menu"
items:
- label: "Reports"
description: "Run system reports"
children:
- label: "System Info"
call: "./scripts/Show-SystemInfo.ps1"
confirm: true
- label: "Process Report"
call: "./scripts/Get-ProcessReport.ps1"
- label: "Settings"
import: "./menus/settings.yaml"
- label: "Exit"
exit: true| Keys present | Node type | Behavior |
|---|---|---|
exit: true |
EXIT | Cleanly quits the menu |
children or import |
BRANCH | Drills into a submenu |
call with .ps1 or path chars |
SCRIPT | Executes a script via safe & call |
call with no extension |
FUNCTION | Calls a whitelisted PowerShell function |
| Key | Required | Notes |
|---|---|---|
label |
yes | Display text shown in the menu |
exit |
no | Set to true to signal a clean quit |
children |
no | Inline list of submenu items |
import |
no | Relative path to an external YAML submenu file |
call |
no | Script path or function name to execute |
params |
no | Hashtable of parameters splatted to call |
confirm |
no | If true, prompts Y/N before executing |
description |
no | Brief subtitle shown under the item while navigating the menu |
details |
no | Longer text shown in the action banner just before the script or function runs. SCRIPT and FUNCTION nodes only. Supports multi-line YAML block scalars (|) |
hotkey |
no | Single character shortcut, case-insensitive |
before |
no | Pre-execution hook, hook object, or ordered list of hooks |
All keys are remappable via the -KeyBindings parameter -- see the Quick Start section for an example.
| Key | Action |
|---|---|
Up / Down |
Navigate items |
Enter / Right Arrow |
Select / drill into submenu |
Left Arrow / Escape |
Go back one level |
Q |
Quit |
H |
Jump to home / root menu |
Home / End |
Jump to first / last item |
PageUp / PageDown |
Scroll long menus |
For users on high-latency connections, slow hardware, or remote/VDI sessions, the default keybinding navigation (using arrow keys to move the selection) can feel sluggish because the UI re-renders on every navigation keypress.
Index Navigation Mode eliminates this by letting you select menu items directly by number:
- Each menu item is prefixed with its 1-based index (e.g.,
1.,2., ...) - Just type the number of the item you want and press Enter (or wait a moment)
- The menu does not re-render as you type; it only updates when you make a selection or use a control key
- Back, Home, and Quit keys still work as usual
- Hotkeys and description sub-lines are suppressed in this mode for clarity
- The selected highlight is not shown—your choice is immediate
- Only the Back, Quit, and Home key bindings are shown in the footer in this mode. Up, Down, and Select bindings are ignored and not displayed.
When to use it:
- If you notice too much lag or flicker when navigating with arrow keys
- If you're on a remote desktop, SSH, or VDI session
- If your terminal is slow to redraw
How to enable:
Pass the -IndexNavigation switch to Start-Menu:
Start-Menu -Path .\menu.yaml -IndexNavigationYou can combine this with custom key bindings, status data, or themes as usual.
Example:
Start-Menu -Path .\menu.yaml -IndexNavigation -KeyBindings $keyBindings -StatusData $statusDataIn index mode, the UI is more responsive on slow systems because it avoids unnecessary TUI refreshing while you are choosing an item.
PSYamlTUI always starts from its built-in default theme values. You can either pass a hashtable to -Theme or pass a YAML/JSON file to -ThemePath.
The runnable examples in Docs/Examples/README.md use external YAML theme files in:
- Docs/Examples/Fixtures/themes/simple.theme.yaml
- Docs/Examples/Fixtures/themes/intermediate.theme.yaml
- Docs/Examples/Fixtures/themes/advanced.theme.yaml
Any key you omit falls back to the Default theme value, so partial overrides are totally fine.
@{
Border = 'DarkCyan' # box-drawing characters and frame
Title = 'White' # menu title text
Breadcrumb = 'DarkGray' # breadcrumb trail
ItemDefault = '' # unselected items -- empty string uses terminal default
ItemSelected = 'Yellow' # highlighted item
ItemHotkey = 'DarkGray' # hotkey character
ItemDescription = 'DarkGray' # subtitle shown under selected item
StatusLabel = 'DarkGray' # status bar key names
StatusValue = 'Cyan' # status bar values
FooterText = 'DarkGray' # footer separator and label text
}All values are [System.ConsoleColor] names.
Simplest YAML theme file:
Border: "DarkGreen"
Title: "White"
Breadcrumb: "Gray"
ItemDefault: ""
ItemSelected: "Yellow"
ItemHotkey: "Cyan"
ItemDescription: "Gray"
StatusLabel: "DarkGray"
StatusValue: "Cyan"
FooterText: "Gray"Also supported:
theme:
Border: "DarkGreen"
Title: "White"Hashtable example:
$theme = @{
Border = 'DarkCyan'
Title = 'White'
ItemSelected = 'Yellow'
}
Start-Menu -Path .\menu.yaml -Theme $themeTheme file example:
Start-Menu -Path .\menu.yaml -ThemePath .\theme.yamlIf you found a bug or have an idea, opening an issue first is helpful (but not required) so we can align on scope.
See CONTRIBUTING.md for more details.
This project is licensed under the MIT License.
Built by Dan Metzler.

