Center a console menu by measuring the current window in character cells, calculating a zero-based column and row, then placing each line with Console.SetCursorPosition. Read Console.WindowWidth and Console.WindowHeight immediately before drawing; never assume an 80×25 terminal.
Center each menu line horizontally and vertically
This approach centers every line independently, so short and long entries can have different left edges:
string[] menuLines =
{
"1. Start",
"2. Settings",
"3. Exit"
};
int width = Console.WindowWidth;
int height = Console.WindowHeight;
int startRow = Math.Max(0, (height - menuLines.Length) / 2);
Console.Clear();
for (int i = 0; i < menuLines.Length; i++)
{
string line = menuLines[i];
int left = Math.Max(0, (width - line.Length) / 2);
int row = startRow + i;
if (row >= height || left >= width)
continue;
Console.SetCursorPosition(left, row);
Console.Write(line);
}
WindowWidth is the number of columns and WindowHeight is the number of rows. SetCursorPosition uses zero-based column and row coordinates; Microsoft documents that invalid coordinates can raise ArgumentOutOfRangeException.
Choose how horizontal alignment should work
Independent-line centering
Use (width - line.Length) / 2 for each line when the visual goal is to center every label itself. This is usually best for a simple list with no border.
#1 Best Overall
Common block centering
For a box or aligned menu, center the widest line once and pad every row to that width. The block then has one shared left and right edge:
using System.Linq;
string[] menuLines =
{
"1. Start",
"2. Settings",
"3. Exit"
};
int menuWidth = menuLines.Max(s => s.Length);
int boxLeft = Math.Max(0, (Console.WindowWidth - menuWidth) / 2);
int boxTop = Math.Max(0, (Console.WindowHeight - menuLines.Length) / 2);
Console.Clear();
for (int i = 0; i < menuLines.Length; i++)
{
string line = menuLines[i].PadRight(menuWidth);
int row = boxTop + i;
if (row >= Console.WindowHeight)
break;
Console.SetCursorPosition(boxLeft, row);
Console.Write(line);
}
PadRight adds trailing spaces until the requested total length; PadLeft does the same with leading spaces. If a string is already longer than the requested width, padding leaves it unchanged.
Rank #2
What the vertical calculation does
(height - menuLines.Length) / 2 leaves approximately equal blank space above and below the menu. Math.Max(0, ...) prevents a negative row when the menu has more lines than the terminal. In a very short window, the menu therefore starts at row zero and may extend beyond the visible area; the bounds check stops drawing once the last row is reached.
Handle terminal resizing safely
Window dimensions can change between draws. Put the rendering code in a method and call it after a resize or after an action that returns to the menu. Re-read both dimensions each time rather than caching them for the lifetime of the program.
Recommended Free Tools
static void DrawMenu(string[] menuLines)
{
int width = Console.WindowWidth;
int height = Console.WindowHeight;
int top = Math.Max(0, (height - menuLines.Length) / 2);
Console.Clear();
for (int i = 0; i < menuLines.Length; i++)
{
int row = top + i;
if (row >= height)
break;
string line = menuLines[i];
int left = Math.Max(0, (width - line.Length) / 2);
if (left >= width)
continue;
Console.SetCursorPosition(left, row);
Console.Write(line);
}
}
If the terminal is resized while the loop is running, a previously valid coordinate can become invalid. Catch ArgumentOutOfRangeException when your application must recover from such a race, then clear and redraw using fresh dimensions.
Redirected output and other failure cases
- Output is redirected: redirected streams do not behave like an interactive window. Console dimension properties or cursor operations can fail, including with
IOException. Detect non-interactive output for programs that may run in pipelines and use plain, non-positioned text instead. - The menu is wider than the window: the arithmetic clamps the left position to zero, but the line can still wrap or be clipped by the terminal. Consider shortening labels, wrapping them deliberately, or requiring a wider window.
- The menu is taller than the window: the top row becomes zero and the bounds check draws only visible rows. A paging or scrolling design is needed if every option must remain accessible.
- Coordinates are invalid: always check the calculated row and column before calling
SetCursorPosition; its API uses zero-based coordinates and can reject values outside the available buffer.
Unicode and display-width caveat
string.Length counts UTF-16 code units, not terminal display cells. Combining marks may occupy no additional cell, while some wide glyphs occupy two. Consequently, arithmetic based on Length can look off-center for menus containing certain non-ASCII characters. For those menus, calculate each line’s terminal display width with a Unicode-aware routine and use that width for centering; continue passing the resulting cell coordinates to the console APIs.
Quick Recap
Best Value
Rank #4
Quick implementation checklist
- Store the menu as a
string[]and decide whether lines or the whole block should be centered. - Read
Console.WindowWidthandConsole.WindowHeightimmediately before rendering. - Calculate a zero-based column and row, clamping the row and column to zero.
- Check bounds before every
SetCursorPositioncall. - Use
PadRight(orPadLeft) when rows need a common width. - Redraw after a resize or other event that may change the available dimensions.
- Provide a non-positioned fallback for redirected or non-interactive output.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




