diff --git a/src/output.cpp b/src/output.cpp
index 5b420ca550299..0e1434be74e62 100644
--- a/src/output.cpp
+++ b/src/output.cpp
@@ -1822,6 +1822,71 @@ std::string get_labeled_bar( const double val, const int width, const std::strin
     return get_labeled_bar( val, width, label, ratings.begin(), ratings.end() );
 }
 
+/**
+ * Inserts a table into a window, with data right-aligned.
+ * @param pad Reduce table width by padding left side.
+ * @param line Line to insert table.
+ * @param columns Number of columns. Can be 1.
+ * @param nc_color &FG Default color of table text.
+ * @param divider To insert a character separating table entries. Can be blank.
+ * @param r_align true for right aligned, false for left aligned.
+ * @param data Text data to fill.
+ * Make sure each entry of the data vector fits into one cell, including divider if any.
+ */
+void insert_table( const catacurses::window &w, int pad, int line, int columns,
+                   const nc_color &FG, const std::string &divider, bool r_align,
+                   const std::vector<std::string> &data )
+{
+    const int width = getmaxx( w );
+    const int rows = getmaxy( w );
+    const int col_width = ( ( width - pad ) / columns );
+    int indent = 1;  // 1 for right window border
+    if( r_align ) {
+        indent = ( col_width * columns ) + 1;
+    }
+    int div = columns - 1;
+    int offset = 0;
+
+#if defined(__ANDROID__)
+    input_context ctxt( "INSERT_TABLE" );
+#endif
+    wattron( w, FG );
+    for( int i = 0; i < rows * columns; i++ ) {
+        if( i + offset * columns >= static_cast<int>( data.size() ) ) {
+            break;
+        }
+        int y = line + ( i / columns );
+        if( r_align ) {
+            indent -= col_width;
+        }
+        if( div != 0 ) {
+            if( r_align ) {
+                right_print( w, y, indent - utf8_width( divider ), FG, divider );
+            } else {
+                fold_and_print_from( w, point( indent + col_width - utf8_width( divider ), y ),
+                                     utf8_width( divider ), 0, FG, divider );
+            }
+            div--;
+        } else {
+            div = columns - 1;
+        }
+
+        if( r_align ) {
+            right_print( w, y, indent, c_white, data[i + offset * columns] );
+            if( indent == 1 ) {
+                indent = ( col_width * columns ) + 1;
+            }
+        } else {
+            fold_and_print_from( w, point( indent, y ), col_width, 0, c_white, data[i + offset * columns] );
+            indent += col_width;
+            if( indent == ( col_width * columns ) + 1 ) {
+                indent = 1;
+            }
+        }
+    }
+    wattroff( w, FG );
+}
+
 scrollingcombattext::cSCT::cSCT( const point &p_pos, const direction p_oDir,
                                  const std::string &p_sText, const game_message_type p_gmt,
                                  const std::string &p_sText2, const game_message_type p_gmt2,
diff --git a/src/output.h b/src/output.h
index 49690e134313a..1734b04bfe006 100644
--- a/src/output.h
+++ b/src/output.h
@@ -323,6 +323,9 @@ void center_print( const catacurses::window &w, int y, const nc_color &FG,
                    const std::string &text );
 int right_print( const catacurses::window &w, int line, int right_indent,
                  const nc_color &FG, const std::string &text );
+void insert_table( const catacurses::window &w, int pad, int line, int columns,
+                   const nc_color &FG, const std::string &divider, bool r_align,
+                   const std::vector<std::string> &data );
 void scrollable_text( const catacurses::window &w, const std::string &title,
                       const std::string &text );
 void scrollable_text( const std::function<catacurses::window()> &init_window,
diff --git a/src/ranged.cpp b/src/ranged.cpp
index 61982516d2fb1..b29043e426bbf 100644
--- a/src/ranged.cpp
+++ b/src/ranged.cpp
@@ -1246,6 +1246,10 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in
         window_width -= bars_pad;
     }
 
+    std::string label_m = _( "Moves" );
+    std::vector<std::string> t_aims( 4 ), t_confidence( 16 );
+    int aim_iter = 0, conf_iter = 0;
+
     nc_color col = c_dark_gray;
 
     std::vector<aim_type> aim_types;
@@ -1277,6 +1281,19 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in
         }
         line_number++;
     }
+    if( ( panel_type == "compact" || panel_type == "labels-narrow" ) && display_type == "numbers" ) {
+        std::string symbols = _( " <color_green>Great</color> - <color_light_gray>Normal</color>"
+                                 " - <color_magenta>Graze</color> - <color_light_blue>Moves</color>" );
+        fold_and_print( w, point( 1, line_number++ ), window_width + bars_pad,
+                        c_dark_gray, symbols );
+        int len = utf8_width( symbols ) - 96; // 96 to subtract color codes
+        if( len > window_width + bars_pad ) {
+            line_number++;
+        }
+        for( int i = 0; i < window_width; i++ ) {
+            mvwprintw( w, point( i + 1, line_number ), "-" );
+        }
+    }
 
     const auto front_or = [&]( const std::string & s, const char fallback ) {
         const auto keys = ctxt.keys_bound_to( s );
@@ -1305,11 +1322,16 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in
         }
 
         auto hotkey = front_or( type.action.empty() ? "FIRE" : type.action, ' ' );
-        if( ( panel_type == "compact" || panel_type == "labels-narrow" ) && display_type != "numbers" ) {
-            print_colored_text( w, point( 1, line_number ), col, col, string_format( _( "%s %s:" ), label,
-                                aim_l ) );
-            right_print( w, line_number++, 1, c_light_blue, _( "Moves" ) );
-            right_print( w, line_number, 1, c_light_blue, string_format( "%d", moves_to_fire ) );
+        if( ( panel_type == "compact" || panel_type == "labels-narrow" ) ) {
+            if( display_type == "numbers" ) {
+                t_aims[aim_iter] = string_format( "<color_dark_gray>%s:</color>", label );
+                t_confidence[( aim_iter * 4 ) + 3] = string_format( "<color_light_blue>%d</color>", moves_to_fire );
+            } else {
+                print_colored_text( w, point( 1, line_number ), col, col, string_format( _( "%s %s:" ), label,
+                                    aim_l ) );
+                right_print( w, line_number++, 1, c_light_blue, _( "Moves" ) );
+                right_print( w, line_number, 1, c_light_blue, string_format( "%d", moves_to_fire ) );
+            }
         } else {
             print_colored_text( w, point( 1, line_number++ ), col, col,
                                 string_format( _( "<color_white>[%s]</color> %s %s: Moves to fire: "
@@ -1320,17 +1342,31 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in
         double confidence = confidence_estimate( range, target_size, current_dispersion );
 
         if( display_type == "numbers" ) {
-            int last_chance = 0;
-            std::string confidence_s = enumerate_as_string( confidence_config.begin(), confidence_config.end(),
-            [&]( const confidence_rating & config ) {
-                // TODO: Consider not printing 0 chances, but only if you can print something (at least miss 100% or so)
-                int chance = std::min<int>( 100, 100.0 * ( config.aim_level * confidence ) ) - last_chance;
-                last_chance += chance;
-                return string_format( "%s: <color_%s>%3d%%</color>", pgettext( "aim_confidence",
-                                      config.label.c_str() ), config.color, chance );
-            }, enumeration_conjunction::none );
-            line_number += fold_and_print_from( w, point( 1, line_number ), window_width, 0,
-                                                c_dark_gray, confidence_s );
+            if( panel_type == "compact" || panel_type == "labels-narrow" ) {
+                int last_chance = 0;
+                for( const confidence_rating &cr : confidence_config ) {
+                    int chance = std::min<int>( 100, 100.0 * ( cr.aim_level ) * confidence ) - last_chance;
+                    last_chance += chance;
+                    t_confidence[conf_iter] = string_format( "<color_%s>%3d%%</color>", cr.color, chance );
+                    conf_iter++;
+                    if( conf_iter == ( aim_iter * 4 ) + 3 ) {
+                        conf_iter++;
+                    }
+                }
+                aim_iter++;
+            } else {
+                int last_chance = 0;
+                std::string confidence_s = enumerate_as_string( confidence_config.begin(), confidence_config.end(),
+                [&]( const confidence_rating & config ) {
+                    // TODO: Consider not printing 0 chances, but only if you can print something (at least miss 100% or so)
+                    int chance = std::min<int>( 100, 100.0 * ( config.aim_level * confidence ) ) - last_chance;
+                    last_chance += chance;
+                    return string_format( "%s: <color_%s>%3d%%</color>", pgettext( "aim_confidence",
+                                          config.label.c_str() ), config.color, chance );
+                }, enumeration_conjunction::none );
+                line_number += fold_and_print_from( w, point( 1, line_number ), window_width, 0,
+                                                    c_dark_gray, confidence_s );
+            }
         } else {
             std::vector<std::tuple<double, char, std::string>> confidence_ratings;
             std::transform( confidence_config.begin(), confidence_config.end(),
@@ -1347,6 +1383,15 @@ static int print_ranged_chance( const player &p, const catacurses::window &w, in
         }
     }
 
+    // Draw tables for compact Numbers display
+    if( ( panel_type == "compact" || panel_type == "labels-narrow" )
+        && display_type == "numbers" ) {
+        const std::string divider = "|";
+        int left_pad = 10, columns = 4;
+        insert_table( w, left_pad, ++line_number, columns, c_light_gray, divider, true, t_confidence );
+        insert_table( w, 0, line_number, 1, c_light_gray, "", false, t_aims );
+        line_number = line_number + 4; // 4 to account for the tables
+    }
     return line_number;
 }
 
@@ -2051,10 +2096,9 @@ target_handler::trajectory target_ui::run( player &pc, ExitCode *exit_code )
 
 void target_ui::init_window_and_input( player &pc )
 {
-    // TODO: make 'narrow' layout work for 'numbers' display type
     std::string display_type = get_option<std::string>( "ACCURACY_DISPLAY" );
     std::string panel_type = panel_manager::get_manager().get_current_layout_id();
-    narrow = ( panel_type == "compact" || panel_type == "labels-narrow" ) && display_type != "numbers";
+    narrow = ( panel_type == "compact" || panel_type == "labels-narrow" );
 
     int top = 0;
     int width;