Compatibility
Minecraft: Java Edition
Platforms
Tags
Creators
Details
Vista
Server menus built on Minecraft's own dialog screens
No chest inventories. No filler panes. No item names pretending to be labels. One YAML file per screen, rendered for each player who opens it.

Why this one?
With other dialog plugins a button runs one command, and that is the whole feature. Vista adds
everything a real menu needs, put price: 1000 on a button and it is a shop that checks their
balance, confirms, takes the money and if they're short, it tells them how short they are; buttons can be hidden from, the wrong ranks, locked behind a level, or marked Owned once bought; players get on/off settings, and player lookup; and the G key and the Escape-menu button work from the first start, with no datapack and no second restart.
Things to build with it
A server hub. The G key opens it, every feature is one press away, and staff-only entries
appear with a show-if on a permission.

A profile. One file, lookup: true, and a search box for anyone else's numbers.

Leaderboards. Body lines of placeholders with <head:%top_1_name%> drawn beside each one.

Kits, warps, crates. A list screen, one button each, click-if for the requirement and
deny for the reason.

Rules and guides. Body text, a button per section, no plugin dependency at all.

Settings. type: toggle buttons that flip on and off in place, remember themselves per
player, and run a command on each side.

The G keybind and the pause button, from first start
Vista automatically adds its G key Quick Actions and Escape menu integration. Just drop the plugin in, start your server, and it works. No datapacks, reloads, or extra setup required.
quick-actions:
enabled: true
dialog: menu
label: 'Menu'
pause-screen:
enabled: true
dialog: menu
label: 'Menu'
Point both at the same dialog and editing that one file changes both.

Wiki
The dialog format
Every key is optional except title and buttons.
Screen
title: 'Crate Keys' # window title. Placeholders work.
external-title: 'Keys' # the label when opened from the pause menu
command: 'keys' # registers /keys
aliases: ['k', 'crates'] # extra names for it
overwrite: true # take /keys even if another plugin has it
permission: 'server.keys' # required to open at all
back: 'shop' # where Back goes when there is no history
columns: 2 # buttons per row
escape: true # can Esc close it
close-button: 'Close' # bottom-right button that closes; omit for none
back-button: 'Back' # or one that goes back; omit for none
currency: 'coins' # what price: is counted in
prefix: '<#3A90E8>SHOP ▶ ' # chat prefix for messages this screen causes
lookup: false # resolve placeholders for a searched player
open-if: # conditions checked before it opens
- '%player_level% >= 5'
| Key | Meaning |
|---|---|
title |
Window title. Placeholders resolve per viewer |
external-title |
Label used when this screen is the pause-menu button |
command |
Registers /thatname, live — no restart |
aliases |
Extra names for that command |
overwrite |
Whether command and aliases take a name another plugin already uses. Default true |
permission |
Node required to open the screen |
back |
Dialog the Back button falls back to |
columns |
Buttons per row. 1 is a tall list, 2 suits a hub |
escape |
Whether Esc closes it. Default true |
close-button |
Text of a bottom-right button that closes. Omit it for none |
back-button |
Text of a bottom-right button that goes back. Omit it for none |
currency |
Which currency price: means here |
prefix |
Chat prefix for messages this screen causes |
lookup |
Resolve placeholders for a searched player instead of the viewer |
open-if |
Conditions, all of which must pass |
Body
Lines of text above the buttons. An empty string is a blank line.
body:
- '<white>Keys go straight into your inventory.'
- '<gray>Choose one below</gray>'
- ''
- '<gray>Balance:</gray> <white>%economy_balance%</white>'
Buttons
Each key under buttons: names one button. The name is yours and shows up in /vista list.
buttons:
vote-key:
label: 'Vote Key'
tooltip:
- '<#3A90E8>Vote Key'
- ''
- '<white>Opens the Vote Crate.'
width: 150
slot: 1
priority: 0
permission: 'server.keys.vote'
show-if:
- '%player_level% >= 10'
click-if:
- '%player_world% == survival'
deny:
- '[message] <red>Only in the survival world.'
price: 100
owned: 'server.perk.fly'
url: 'https://example.com'
run:
- '[console] crates give %player_name% vote %amount%'
| Key | Meaning |
|---|---|
label |
The button text |
tooltip |
A list of lines, or one string using <br> |
width |
Pixels, 1–1024. Default 150 |
slot |
Draw order. Buttons sharing one are resolved by priority |
priority |
Lower wins the slot. Default 0 |
permission |
Node required to see and press it |
show-if |
Conditions; failing hides the button |
click-if |
Conditions; failing blocks the press and runs deny |
deny |
Actions run when click-if fails |
price |
Cost in the screen's currency |
amounts |
Quantity-to-price map, for anything sold in sizes |
owned |
Permission meaning "already bought" |
url |
Opens a link instead of running anything |
with-permission |
Permissions the player holds only while this button's actions run |
type: toggle |
Makes it a switch over a setting named after the button |
for-each: items |
Repeats the button once per item in the game — see Item lists |
search |
With for-each, the input whose text narrows the list |
run |
The actions, in order |
Inputs
Text fields, drawn above the buttons and written like buttons: a name, then what it is. A button
reads one back as $(name).
inputs:
player:
label: 'Look up a player'
max-length: 16
initial: '%player_name%'
width: 200
search:
label: 'Search'
run:
- '[player] find $(player)'
$(name) works in conditions too, so a click-if can check what was typed before anything runs,
and in an input's own initial: — initial: '$(search)' keeps the text in the box when the
screen redraws. Opening a screen starts its inputs empty.
What was typed can be tidied on the way in:
| Written | " My Home " becomes |
|---|---|
$(name|trim) |
My Home — outer and doubled spaces dropped |
$(name|lower) / $(name|upper) |
my home / MY HOME |
$(name|slug) |
my_home — one safe word, for ids and commands |
They chain, left to right: $(name|trim|upper).
Actions
run: is a list, carried out in order. A chain stops at the first step that refuses.
| Action | Does |
|---|---|
[player] <command> |
Runs the command as the player |
[console] <command> |
Runs it as the console |
[message] <text> |
Sends the player a message |
[broadcast] <text> |
Sends it to everyone |
[log] <text> |
Writes a console line |
[sound] <name> [volume] [pitch] |
Plays a sound |
[open] <dialog> |
Opens another dialog |
[server] <name> |
Sends the player to that server through the proxy |
[back] |
Goes back one screen |
[close] |
Closes the screen |
[refresh] |
Redraws it in place |
[lookup] <player> |
Makes the next screen describe that player |
[set] <name> <value> |
Remembers text for this player — see Variables |
[unset] <name> |
Forgets it |
[givepermission] <node> |
Grants a permission |
[takepermission] <node> |
Removes one |
An untagged line is treated as [message].
A step can also be a choice, made when the chain reaches it:
run:
- if: '$(name|slug) != %vista_var_home%'
run:
- '[player] essentials:renamehome %vista_var_home% $(name|slug)'
else:
- '[message] Same home, new spelling.'
- '[open] homes-manage'
if: takes any condition line, or a list of them. else: is optional.
[server] is how a server selector works. /server belongs to the proxy, not to the server the
player is standing on, so running it as them does nothing; [server] survival asks the proxy
directly over the BungeeCord plugin channel, which Velocity answers as well. Nothing to install
or configure on either side.
There is no click-sound setting, on purpose: the client plays its own click when a dialog button
is pressed, so a second from the server doubles it up. A button that wants a particular noise
asks with [sound].
Conditions
show-if hides a button. click-if lets it be seen but blocks the press. Both are lists, and
every line must pass. A leading ! inverts one.
show-if:
- '%player_level% >= 30'
- '%player_world% == survival'
- 'has permission server.vip'
- '!has permission server.banned'
- 'has any permission server.vip server.mvp'
- '%player_prefix% contains Gold'
- '$(name) matches [A-Za-z0-9_]{1,16}'
| Operator | Compares |
|---|---|
> >= < <= |
Numbers |
== != |
Numbers when both sides are numeric, text otherwise |
equals equalsignorecase |
Text |
contains |
Text |
matches |
A regular expression |
has permission <node> |
One permission |
has any permission <a> <b> |
Any of several |
has setting <name> |
One of the player's own on/off settings |
|| joins lines of which any one is enough:
- '%player_level% >= 30 || has permission server.vip'
A click-if line can carry its own message. Only the first line that fails is reported, so
the player is told exactly what is wrong; lines without one fall back to the button's deny:.
click-if:
- if: '$(name|trim) != '
deny: '[message] <red>Type a name first.'
- if: '$(name|trim) matches [A-Za-z0-9_ ]{1,16}'
deny: '[message] <red>Letters, numbers, spaces and _ only.'
- '%player_level% >= 5'
deny:
- '[message] <red>You need level 5.'
A line that cannot be read is dropped and logged, so one typo cannot break a screen.
One slot, several buttons
Buttons sharing a slot: are checked in ascending priority:, and the first whose show-if
passes is the one drawn. That is how a single screen shows an entry as claimed, current or
available without three separate files.
rank-claimed:
label: 'Rank 2'
slot: 1
priority: 1
show-if: ['%player_rank% > 2']
run: ['[message] <gray>Already claimed.']
rank-current:
label: 'Rank 2'
slot: 1
priority: 2
show-if: ['%player_rank% == 2']
run: ['[message] <gray>This is your rank.']
rank-available:
label: 'Rank 2'
slot: 1
priority: 3
run: ['[console] rank set %player_name% 2']
Settings and switches
A button with type: toggle is a switch. Pressing it flips the setting, runs the matching side
at once, and the screen redraws where it stands: a screen holding a switch is sent as one
that stays open, so the press closes nothing and nothing is reopened.
buttons:
pvp:
type: 'toggle'
label: 'PvP'
default: true
when-on:
- '[console] pvp enable %player_name%'
when-off:
- '[console] pvp disable %player_name%'
| Key | Meaning |
|---|---|
type: toggle |
Makes the button a switch |
label |
What it is called. Drawn as PvP: On |
default |
What the setting is before the player has ever pressed it |
setting |
The setting it switches. Defaults to the button's own name |
state-if |
Reads the state from a condition instead of remembering it |
when-on / when-off |
Actions run the moment it lands on that side |
The button's name is the setting, remembered per player in players.yml. Naming it creates it;
there is nothing to declare anywhere else.
PvP: On is toggle-label in lang.yml, and the two words are state-on and state-off, so
every switch on the server is reworded in one place. A label that writes <state> itself is used
as written.
Reading a setting elsewhere
| Where | How |
|---|---|
| Any text | %vista_setting_pvp% — prints On or Off |
| Any condition | has setting pvp, or !has setting pvp |
A setting another plugin owns
state-if: reads the state from a condition rather than remembering it, so the switch and the
plugin behind it can never disagree.
fly:
type: 'toggle'
label: 'Flight'
state-if:
- '%player_is_flying% == true'
when-on:
- '[player] fly on'
when-off:
- '[player] fly off'
Nothing is stored for one of these; the condition answers every time the screen is drawn.
Variables
On/off is not always enough. [set] remembers any text for the player who pressed the button,
and %vista_var_<name>% reads it back anywhere — a label, a body line, a condition, another
command.
pick-base:
label: 'Base'
run:
- '[set] home base'
- '[open] home-screen'
# home-screen.yml
title: '%vista_var_home%'
buttons:
teleport:
label: 'Teleport'
run:
- '[player] home %vista_var_home%'
That is how one screen serves many things: the button that opens it says which one. Variables
are kept per player in players.yml, survive restarts, and read as nothing until they are set.
[unset] home forgets one.
Defaults
A dialog can say what a variable reads before anything has been set. A * covers every name
that starts with the rest:
variables:
homeicon_*: '<sprite:block/red_wool>'
homename_*: '*'
Every home now shows red wool until the player picks something else. A * in the value is
whatever the name's * matched, so %vista_var_homename_base% reads base until a nicer name
is set. Defaults apply
everywhere, whichever dialog declares them, so a set of screens that belong together can carry
their own.
A placeholder inside a placeholder
Write the inner one in braces and it is filled in first:
- '%vista_var_icon_{vista_var_home}%'
With home set to base, that reads %vista_var_icon_base%. Any placeholder works in braces,
including other plugins' — ordinary braces in text are left alone.
Commands players can't type
with-permission: gives the player a permission for as long as that one button's actions run,
then takes it straight back. A menu can use a command your players have no access to — so
/sethome can live behind a screen that checks the name first, instead of in chat.
create:
label: 'Create'
click-if:
- '$(name|trim) matches [A-Za-z0-9_ ]{1,16}'
with-permission:
- 'essentials.sethome'
run:
- '[player] essentials:sethome $(name|slug)'
- '[set] homename_$(name|slug) $(name|trim)'
Only [player] commands in that button's chain benefit, and only while they run.
Item lists
for-each: items turns one button into one per item in the game, drawn where the button is
written. Three tokens fill in for each item:
| Token | Is |
|---|---|
<icon> |
The item's sprite, drawn in the text |
<name> |
Its name, in each player's own language |
<id> |
Its id, e.g. diamond_sword |
columns: 4
inputs:
search:
label: 'Search items'
initial: '$(search)'
buttons:
search:
label: 'Search'
run:
- '[refresh]'
icon:
for-each: 'items'
search: 'search'
label: '<icon> <name>'
run:
- '[set] icon_%vista_var_home% <icon>'
- '[open] home-screen'
search: names the input that narrows the list: type "sword", press Search, and only swords are
left. It matches English names and ids, so "bottle o" finds the Bottle o' Enchanting.
A stored <icon> is a sprite wherever it is shown afterwards, so %vista_var_icon_base% in a
label draws the item. Each item's button is built once and reused, so a screen of fifteen hundred
opens as fast as a screen of ten. Beds are drawn whole, both halves side by side. Chests, shields
and mob heads have no flat sprite in the game's textures and show only their name.
Shops
The whole of it
Define a currency once:
currencies:
coins:
name: 'Coins'
balance: '%economy_balance%'
take: 'eco take %player_name% %amount%'
format: '<price> <currency>'
Name it at the top of a screen, and each entry states only its price:
currency: coins
buttons:
diamond:
label: 'Diamond'
price: 100
run:
- '[console] give %player_name% diamond %amount%'
That one price: gives you the price line in the tooltip, a balance check before anything is
taken, a confirmation screen, the deduction, the entry's own command, a receipt, a sound, the
screens closing, and a refusal naming the cost and the balance when there isn't enough.
Nothing about money appears in the dialog file.
Sizes
Give each size its own price. Bulk does not have to be a multiple. %amount% becomes the
quantity, and pressing the entry opens a quantity screen first.
crate-key:
label: 'Crate Key'
amounts:
1: 100
3: 250
run:
- '[console] crates give %player_name% vote %amount%'
Already owned
owned: 'server.perk.fly'
Once the player has that permission the entry recolours, reads Owned and stops responding. It stays on the screen so the catalogue is still readable instead of shrinking.
Recolouring means exactly that and nothing else. Affordability never changes how an entry looks: one the player cannot pay for is pressable, and the refusal arrives in chat.
How money reads
format: decides how an amount is written everywhere at once — tooltips, the quantity screen,
the confirmation, the receipt, the refusal.
format: '<price> <currency>' # 500 Coins
format: '$<price>' # $500
format: '<raw> Cash' # 500 Cash, no thousands separator
format: 'You need <price>' # You need 500
format: '<price>⛃' # 500⛃, a glyph from your resource pack
Add as many currencies as your server has. A currency is a balance placeholder and a take command, so anything that exposes those works.
Everything the player reads
Both screens Vista builds for you — the quantity picker and the confirmation — have every line
in lang.yml, including their titles and both buttons. Nothing is compiled in.
quantity-title: "How many?"
quantity-body:
- "<white>Choose how many <accent><name><white> to buy."
- ""
- "<gray>Your balance:</gray> <white><money>"
quantity-option: "<white><amount> <dark_gray>|</dark_gray> <accent><money>"
quantity-tooltip: "<accent><amount>x <name><br><br><white>Costs <accent><money><white>."
confirm-title: "Confirm Purchase"
confirm-body:
- "<white>Buy <accent><amount>x <name><white> for <accent><money><white>?"
- ""
- "<gray>Your balance:</gray> <white><balance>"
- "<gray>After this purchase:</gray> <white><after>"
button-confirm: "<green>Confirm"
button-confirm-tooltip: "<green>Confirm<br><br><white>Complete the purchase."
button-cancel: "<gray>Cancel"
The body lists take as many or as few lines as you like. Tokens available on them:
| Token | Is |
|---|---|
<name> |
What is being bought |
<amount> |
How many |
<money> |
The cost, written the way that currency's format: says |
<price> <currency> |
The same, split apart |
<balance> |
What the player has |
<after> |
What they will have |
Column counts are buying.quantity-columns and buying.confirm-columns.
The wording every switch uses lives here too, so On and Off are said your way everywhere at once:
toggle-label: "<white><label>: <state>"
state-on: "<green>On"
state-off: "<gray>Off"
Prefixes
Three, all separate and all yours:
prefix: "<#3A90E8><b>VISTA </b><gray>▶</gray> "
error-prefix: "<#F43F5E><b>ERROR </b><gray>▶</gray> "
accent: "<#3A90E8>"
owned-color: "<#3A90E8>"
prefix is Vista talking about itself. error-prefix is every refusal, and a dialog can never
override it. A dialog's own prefix: covers the messages it causes, so a shop's receipts say
SHOP instead. accent recolours every screen Vista builds; owned-color is what an owned
entry is drawn in.
Every refusal works the same way: a red ERROR line, and the screen closes. Nobody is left looking at a button that will not work.
Colours and text
MiniMessage throughout, with legacy &7 and &#RRGGBB codes accepted in the same string, so a
line lifted from an older config renders without being rewritten.
- '<gradient:#3A90E8:#8C4DCC>Gradient text</gradient>'
- '<hover:show_text:"A tip"><u>Hover me</u></hover>'
- '<head:Notch> drawn inline with the text'
- '<sprite:items:item/diamond> any item sprite, <sprite:block/stone> any block'
- '&7Legacy grey and A90E8hex, in the same line as <white>MiniMessage'
Player lookup
A screen with lookup: true resolves its placeholders for whoever the viewer searched for
instead of for the viewer, so one file serves as everyone's profile.
lookup: true
title: '%player_name%'
body:
- '<head:%player_name%>'
- '<gray>Kills:</gray> <white>%statistic_player_kills%</white>'
inputs:
player:
label: 'Look up a player'
buttons:
search:
label: 'Search'
run:
- '[lookup] $(player)'
- '[refresh]'
Part of a name is enough. An exact name wins outright, a single match opens straight away, and several matches put up a Did you mean? screen — one button per player, each carrying their head, closest match first. Nothing resolves until one is pressed.
%vista_looking_at% is empty on your own profile and holds the searched name otherwise, which is
enough to swap a Back button between two destinations with show-if. %vista_dialog% is the
current dialog id. Both are answered by Vista itself.
Links
discord:
label: 'Join the Discord'
tooltip:
- '<#5865F2>Join the Discord'
- ''
- '<white>Opens in your browser.'
url: 'https://discord.gg/ripplemc'
The client shows its own confirmation and opens the browser. No chat message with a link in it, no command in between. Placeholders work in the address.
Commands
| Command | Does |
|---|---|
/vista open <dialog> [player] |
Opens a dialog |
/vista list |
Every loaded dialog, with its file and button count |
/vista reload |
Reloads the config, the language file and every dialog |
/vista instantbuy |
Toggles your own purchase confirmations |
/dialog open <id> [player] |
The same as /vista open |
/dialog list |
The same as /vista list |
vista.admin covers listing, reloading and opening a dialog for someone else.
Any screen with a command: gets its own command, registered live. Add a file, run
/vista reload, and /yourdialog works — no restart.
If another plugin already has that name, the dialog takes it — command: playtime replaces their
/playtime — and gives it back if the dialog is removed or reloaded with overwrite: false. With
overwrite: false the dialog leaves their command alone and stays reachable as /vista:playtime.
A reload that finds something wrong in a file says so in chat, and the console says what and where.
Minecraft has had its own /dialog since 1.21.6. Vista takes the plain label and leaves the
vanilla one reachable as /minecraft:dialog; commands.dialog: false leaves it alone.
Getting started
- Drop the jar in
plugins/and start the server. - Vista writes
config.yml,lang.ymland four example dialogs. - Open
plugins/Vista/dialogs/menu.yml— it is commented as a tutorial. - Press G in game.
menu.yml covers the basics, shop.yml covers selling, profile.yml covers player lookup. Copy
whichever is closest to what you want.
Updating: replace the jar. Settings added in a newer version are written into your
config.yml and lang.yml on the next start, in the section they belong to and with the comment
that explains them. Your values, ordering and your own notes are left alone, and example
currencies you removed are not put back.
Requirements
Server: Paper or Folia 1.21.7 or newer, Java 21. Minecraft added dialogs in 1.21.6, but the server-side API for building them arrived in 1.21.7, and that is what Vista uses. On Folia every click, command and redraw runs on the region that owns the player.
Players: a 1.21.6 or newer client. Dialogs render client-side, so an older client cannot see one at all. Player heads and sprites in text additionally need 1.21.9 — on 1.21.6 to 1.21.8 the tag is skipped and the name still shows — and item sprites need 1.21.11, when the game gave items their own texture atlas.
Dependencies: none. PlaceholderAPI is used when it is there and ignored when it is not, and nothing else is required at all. The jar is around 100 KB and bundles nothing.
Open source all rights reserved.


