If you only want a very simple menu that shows "in place" and you can continue typing after that - without any fancy external dialog program, then you can use ANSI escape sequences and a simple loop to render the list and allow a cursor to be moved on top of it.
The answer here by user360154 already has everything you need, but it is also super fancy, does much more than needed and while the code is also formatted to look fancy - it isn't easy to read and understand.
Here's the same approach as user360154's but much simpler:
function choose_from_menu() {
local -r prompt="$1" outvar="$2" options=("${@:3}")
local cur=0 count=${#options[@]} index=0
local esc=$(echo -en "\e") # cache ESC as test doesn't allow esc codes
printf "$prompt\n"
while true
do
# list all options (option list is zero-based)
index=0
for o in "${options[@]}"
do
if [ "$index" == "$cur" ]
then echo -e " >\e[7m$o\e[0m" # mark & highlight the current option
else echo " $o"
fi
(( index++ ))
done
read -s -n3 key # wait for user to key in arrows or ENTER
if [[ $key == $esc[A ]] # up arrow
then (( cur-- )); (( cur < 0 )) && (( cur = 0 ))
elif [[ $key == $esc[B ]] # down arrow
then (( cur++ )); (( cur >= count )) && (( cur = count - 1 ))
elif [[ $key == "" ]] # nothing, i.e the read delimiter - ENTER
then break
fi
echo -en "\e[${count}A" # go up to the beginning to re-render
done
# export the selection to the requested output variable
printf -v $outvar "${options[$cur]}"
}
Here is an example usage:
selections=(
"Selection A"
"Selection B"
"Selection C"
)
choose_from_menu "Please make a choice:" selected_choice "${selections[@]}"
echo "Selected choice: $selected_choice"
Which should look like this:

Update:
While the above code is for Bash, @lukeflo ported the code to zsh, as he commented below. He has moved things around, so disregard the links in the comments and his example implementation can currently be found here, but if it goes missing (again) a snapshot can be found here.