diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp
index 463d76bb8f..46bab51963 100644
--- a/common/plotters/PDF_plotter.cpp
+++ b/common/plotters/PDF_plotter.cpp
@@ -739,6 +739,23 @@ void PDF_PLOTTER::ClosePage()
         m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
     }
 
+    for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
+    {
+        const BOX2I&                 box = menuPair.first;
+        const std::vector<wxString>& urls = menuPair.second;
+
+        VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
+        VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
+
+        BOX2D userSpaceBox;
+        userSpaceBox.SetOrigin( bottomLeft );
+        userSpaceBox.SetEnd( topRight );
+
+        hyperlinkHandles.push_back( allocPdfObject() );
+
+        m_hyperlinkMenuHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, urls } } );
+    }
+
     int hyperLinkArrayHandle = -1;
 
     // If we have added any annotation links, create an array containing all the objects
@@ -793,6 +810,7 @@ void PDF_PLOTTER::ClosePage()
 
     // Clean up
     m_hyperlinksInPage.clear();
+    m_hyperlinkMenusInPage.clear();
 }
 
 
@@ -804,7 +822,9 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
     m_xrefTable.clear();
     m_xrefTable.push_back( 0 );
     m_hyperlinksInPage.clear();
+    m_hyperlinkMenusInPage.clear();
     m_hyperlinkHandles.clear();
+    m_hyperlinkMenuHandles.clear();
 
     /* The header (that's easy!). The second line is binary junk required
        to make the file binary from the beginning (the important thing is
@@ -878,14 +898,12 @@ bool PDF_PLOTTER::EndPlot()
     fputs( ">>\n", m_outputFile );
     closePdfObject();
 
-    for( const std::pair<const int, std::pair<BOX2D, wxString>>& handlePair : m_hyperlinkHandles )
+    for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
     {
-        const int&                        linkhandle = handlePair.first;
-        const std::pair<BOX2D, wxString>& linkpair = handlePair.second;
-        const BOX2D&                      box = linkpair.first;
-        const wxString&                   url = linkpair.second;
+        const BOX2D&    box = linkPair.first;
+        const wxString& url = linkPair.second;
 
-        startPdfObject( linkhandle );
+        startPdfObject( linkHandle );
 
         fprintf( m_outputFile,
                  "<< /Type /Annot\n"
@@ -904,7 +922,7 @@ bool PDF_PLOTTER::EndPlot()
                 if( m_pageNumbers[ii] == pageNumber )
                 {
                     fprintf( m_outputFile,
-                             "   /Dest [%d 0 R /FitB] >>\n"
+                             "   /Dest [%d 0 R /FitB]\n"
                              ">>\n",
                              m_pageHandles[ii] );
 
@@ -932,6 +950,60 @@ bool PDF_PLOTTER::EndPlot()
         closePdfObject();
     }
 
+    for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
+    {
+        const BOX2D&                 box = menuPair.first;
+        const std::vector<wxString>& urls = menuPair.second;
+
+        // We currently only support menu links for internal pages.  This vector holds the
+        // page names and numbers.
+        std::vector<std::pair<wxString, int>> pages;
+
+        for( const wxString& url : urls )
+        {
+            wxString pageNumber;
+
+            if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
+            {
+                for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
+                {
+                    if( m_pageNumbers[ii] == pageNumber )
+                        pages.push_back( { pageNumber, ii } );
+                }
+            }
+        }
+
+        wxString js = wxT( "var aParams = [ " );
+
+        for( const std::pair<wxString, int>& page : pages )
+        {
+            js += wxString::Format( wxT( "{ cName: '%s', cReturn: '%d' }, " ),
+                                    page.first,
+                                    page.second );
+        }
+
+        js += wxT( "]; " );
+
+        js += wxT( "var cChoice = app.popUpMenuEx.apply\\( app, aParams \\); " );
+        js += wxT( "if\\( cChoice != null \\) this.pageNum = cChoice; " );
+
+        startPdfObject( menuHandle );
+
+        fprintf( m_outputFile,
+                 "<< /Type /Annot\n"
+                 "   /Subtype /Link\n"
+                 "   /Rect [%g %g %g %g]\n"
+                 "   /Border [16 16 0]\n",
+                 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
+
+        fprintf( m_outputFile,
+                 "   /A << /Type /Action /S /JavaScript /JS (%s) >>\n"
+                 ">>\n",
+                 js.ToStdString().c_str() );
+
+        closePdfObject();
+    }
+
     /* The page tree: it's a B-tree but luckily we only have few pages!
        So we use just an array... The handle was allocated at the beginning,
        now we instantiate the corresponding object */
@@ -1079,3 +1151,8 @@ void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationU
     m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
 }
 
+
+void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
+{
+    m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
+}
\ No newline at end of file
diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp
index 39ba4879de..cab34c7a40 100644
--- a/eeschema/sch_field.cpp
+++ b/eeschema/sch_field.cpp
@@ -53,10 +53,9 @@
 #include <trigo.h>
 #include <eeschema_id.h>
 #include <tool/tool_manager.h>
-#include <tools/ee_actions.h>
+#include <tools/sch_navigate_tool.h>
 #include <font/outline_font.h>
 
-
 SCH_FIELD::SCH_FIELD( const VECTOR2I& aPos, int aFieldId, SCH_ITEM* aParent,
                       const wxString& aName ) :
     SCH_ITEM( aParent, SCH_FIELD_T ),
@@ -740,53 +739,36 @@ void SCH_FIELD::DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const
 {
     constexpr int START_ID = 1;
 
-    static int back = -1;
-    wxMenu          menu;
-    SCH_TEXT*       label = dynamic_cast<SCH_TEXT*>( m_parent );
-
-    if( label && Schematic() )
+    if( IsHypertext() )
     {
-        auto it = Schematic()->GetPageRefsMap().find( label->GetText() );
+        SCH_LABEL_BASE*                            label = static_cast<SCH_LABEL_BASE*>( m_parent );
+        std::vector<std::pair<wxString, wxString>> pages;
+        wxMenu                                     menu;
+        wxString                                   href;
 
-        if( it != Schematic()->GetPageRefsMap().end() )
+        label->GetIntersheetRefs( &pages );
+
+        for( int i = 0; i < (int) pages.size(); ++i )
         {
-            std::vector<int> pageListCopy;
+            menu.Append( i + START_ID, wxString::Format( _( "Go to Page %s (%s)" ),
+                                                         pages[i].first,
+                                                         pages[i].second ) );
+        }
 
-            pageListCopy.insert( pageListCopy.end(), it->second.begin(), it->second.end() );
-            if( !Schematic()->Settings().m_IntersheetRefsListOwnPage )
-            {
-                int currentPage = Schematic()->CurrentSheet().GetVirtualPageNumber();
-                alg::delete_matching( pageListCopy, currentPage );
+        menu.AppendSeparator();
+        menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) );
 
-                if( pageListCopy.empty() )
-                    return;
-            }
+        int sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID;
 
-            std::sort( pageListCopy.begin(), pageListCopy.end() );
+        if( sel >= 0 && sel < (int) pages.size() )
+            href = wxT( "#" ) + pages[ sel ].first;
+        else if( sel == 999 )
+            href = SCH_NAVIGATE_TOOL::g_BackLink;
 
-            std::map<int, wxString> sheetNames = Schematic()->GetVirtualPageToSheetNamesMap();
-            std::map<int, wxString> sheetPages = Schematic()->GetVirtualPageToSheetPagesMap();
-
-            for( int i = 0; i < (int) pageListCopy.size(); ++i )
-            {
-                menu.Append( i + START_ID, wxString::Format( _( "Go to Page %s (%s)" ),
-                                                             sheetPages[ pageListCopy[i] ],
-                                                             sheetNames[ pageListCopy[i] ] ) );
-            }
-
-            menu.AppendSeparator();
-            menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) );
-
-            int   sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID;
-            void* param = nullptr;
-
-            if( sel >= 0 && sel < (int) pageListCopy.size() )
-                param = (void*) &pageListCopy[ sel ];
-            else if( sel == 999 )
-                param = (void*) &back;
-
-            if( param )
-                aFrame->GetToolManager()->RunAction( EE_ACTIONS::hypertextCommand, true, param );
+        if( !href.IsEmpty() )
+        {
+            SCH_NAVIGATE_TOOL* navTool = aFrame->GetToolManager()->GetTool<SCH_NAVIGATE_TOOL>();
+            navTool->HypertextCommand( m_hyperlink );
         }
     }
 }
@@ -978,6 +960,20 @@ void SCH_FIELD::Plot( PLOTTER* aPlotter, bool aBackground ) const
 
     aPlotter->Text( textpos, color, GetShownText(), orient, GetTextSize(),  hjustify, vjustify,
                     penWidth, IsItalic(), IsBold(), false, GetDrawFont() );
+
+    if( IsHypertext() )
+    {
+        SCH_LABEL_BASE*                            label = static_cast<SCH_LABEL_BASE*>( m_parent );
+        std::vector<std::pair<wxString, wxString>> pages;
+        std::vector<wxString>                      pageHrefs;
+
+        label->GetIntersheetRefs( &pages );
+
+        for( const std::pair<wxString, wxString>& page : pages )
+            pageHrefs.push_back( wxT( "#" ) + page.first );
+
+        aPlotter->HyperlinkMenu( GetBoundingBox(), pageHrefs );
+    }
 }
 
 
diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp
index 35207084a8..431f08c875 100644
--- a/eeschema/sch_label.cpp
+++ b/eeschema/sch_label.cpp
@@ -428,6 +428,39 @@ void SCH_LABEL_BASE::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
 }
 
 
+void SCH_LABEL_BASE::GetIntersheetRefs( std::vector<std::pair<wxString, wxString>>* pages )
+{
+    if( Schematic() )
+    {
+        auto it = Schematic()->GetPageRefsMap().find( GetText() );
+
+        if( it != Schematic()->GetPageRefsMap().end() )
+        {
+            std::vector<int> pageListCopy;
+
+            pageListCopy.insert( pageListCopy.end(), it->second.begin(), it->second.end() );
+
+            if( !Schematic()->Settings().m_IntersheetRefsListOwnPage )
+            {
+                int currentPage = Schematic()->CurrentSheet().GetVirtualPageNumber();
+                alg::delete_matching( pageListCopy, currentPage );
+
+                if( pageListCopy.empty() )
+                    return;
+            }
+
+            std::sort( pageListCopy.begin(), pageListCopy.end() );
+
+            std::map<int, wxString> sheetPages = Schematic()->GetVirtualPageToSheetPagesMap();
+            std::map<int, wxString> sheetNames = Schematic()->GetVirtualPageToSheetNamesMap();
+
+            for( int pageNum : pageListCopy )
+                pages->push_back( { sheetPages[ pageNum ], sheetNames[ pageNum ] } );
+        }
+    }
+}
+
+
 bool SCH_LABEL_BASE::ResolveTextVar( wxString* token, int aDepth ) const
 {
     if( token->Contains( ':' ) )
diff --git a/eeschema/sch_label.h b/eeschema/sch_label.h
index 010f26adda..1cc160c523 100644
--- a/eeschema/sch_label.h
+++ b/eeschema/sch_label.h
@@ -113,6 +113,12 @@ public:
 
     void AutoplaceFields( SCH_SCREEN* aScreen, bool aManual ) override;
 
+    /**
+     * Builds an array of { pageNumber, pageName } pairs.
+     * @param pages [out] Array of { pageNumber, pageName } pairs.
+     */
+    void GetIntersheetRefs( std::vector<std::pair<wxString, wxString>>* pages );
+
     virtual bool ResolveTextVar( wxString* token, int aDepth ) const;
 
     wxString GetShownText( int aDepth = 0 ) const override;
diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp
index c478c7eceb..7e72710649 100644
--- a/eeschema/tools/ee_actions.cpp
+++ b/eeschema/tools/ee_actions.cpp
@@ -779,10 +779,6 @@ TOOL_ACTION EE_ACTIONS::showHierarchy( "eeschema.EditorTool.showHierarchy",
         _( "Hierarchy Navigator" ), _( "Show or hide the schematic sheet hierarchy navigator" ),
         BITMAPS::hierarchy_nav );
 
-TOOL_ACTION EE_ACTIONS::hypertextCommand( "eeschema.NavigateTool.hypertextCommand",
-        AS_GLOBAL, 0, "",
-        _( "Navigate to page" ), _( "Navigate to page" ) );
-
 
 // SCH_LINE_WIRE_BUS_TOOL
 //
diff --git a/eeschema/tools/sch_navigate_tool.cpp b/eeschema/tools/sch_navigate_tool.cpp
index 1a8a731d0d..c568b74bc7 100644
--- a/eeschema/tools/sch_navigate_tool.cpp
+++ b/eeschema/tools/sch_navigate_tool.cpp
@@ -28,6 +28,10 @@
 #include <tools/sch_navigate_tool.h>
 #include "eda_doc.h"
 
+
+wxString SCH_NAVIGATE_TOOL::g_BackLink = wxT( "HYPERTEXT_BACK" );
+
+
 void SCH_NAVIGATE_TOOL::ResetHistory()
 {
     m_navHistory.clear();
@@ -53,39 +57,16 @@ void SCH_NAVIGATE_TOOL::CleanHistory()
 }
 
 
-int SCH_NAVIGATE_TOOL::HypertextCommand( const TOOL_EVENT& aEvent )
-{
-    int* page = aEvent.Parameter<int*>();
-
-    wxCHECK( page, 0 );
-
-    auto goToPage =
-            [&]( int* aPage )
-            {
-                for( const SCH_SHEET_PATH& sheet : m_frame->Schematic().GetSheets() )
-                {
-                    if( sheet.GetVirtualPageNumber() == *aPage )
-                    {
-                        changeSheet( sheet );
-                        return;
-                    }
-                }
-            };
-
-    if( *page == -1 )
-        Back( aEvent );
-    else
-        goToPage( page );
-
-    return 0;
-}
-
-
 void SCH_NAVIGATE_TOOL::HypertextCommand( const wxString& href )
 {
     wxString destPage;
 
-    if( EDA_TEXT::IsGotoPageHref( href, &destPage ) && !destPage.IsEmpty() )
+    if( href == SCH_NAVIGATE_TOOL::g_BackLink )
+    {
+        TOOL_EVENT dummy;
+        Back( dummy );
+    }
+    else if( EDA_TEXT::IsGotoPageHref( href, &destPage ) && !destPage.IsEmpty() )
     {
         for( const SCH_SHEET_PATH& sheet : m_frame->Schematic().GetSheets() )
         {
@@ -245,7 +226,6 @@ void SCH_NAVIGATE_TOOL::setTransitions()
     Go( &SCH_NAVIGATE_TOOL::ChangeSheet,           EE_ACTIONS::changeSheet.MakeEvent() );
     Go( &SCH_NAVIGATE_TOOL::EnterSheet,            EE_ACTIONS::enterSheet.MakeEvent() );
     Go( &SCH_NAVIGATE_TOOL::LeaveSheet,            EE_ACTIONS::leaveSheet.MakeEvent() );
-    Go( &SCH_NAVIGATE_TOOL::HypertextCommand,      EE_ACTIONS::hypertextCommand.MakeEvent() );
 
     Go( &SCH_NAVIGATE_TOOL::Up,                    EE_ACTIONS::navigateUp.MakeEvent() );
     Go( &SCH_NAVIGATE_TOOL::Forward,               EE_ACTIONS::navigateForward.MakeEvent() );
diff --git a/eeschema/tools/sch_navigate_tool.h b/eeschema/tools/sch_navigate_tool.h
index 9991c01704..b87f5cd0d0 100644
--- a/eeschema/tools/sch_navigate_tool.h
+++ b/eeschema/tools/sch_navigate_tool.h
@@ -64,7 +64,6 @@ public:
     int Previous( const TOOL_EVENT& aEvent );
     ///< Navigate to next sheet by numeric sheet number
     int Next( const TOOL_EVENT& aEvent );
-    int HypertextCommand( const TOOL_EVENT& aEvent );
 
     void HypertextCommand( const wxString& href );
 
@@ -74,6 +73,9 @@ public:
     bool CanGoPrevious();
     bool CanGoNext();
 
+public:
+    static wxString g_BackLink;
+
 private:
     ///< Set up handlers for various events.
     void setTransitions() override;
diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h
index 0b708519bd..b9f9638db2 100644
--- a/include/plotters/plotter.h
+++ b/include/plotters/plotter.h
@@ -447,6 +447,17 @@ public:
         // NOP for most plotters.
     }
 
+    /**
+     * Create a clickable hyperlink menu with a rectangular click area
+     *
+     * @aBox is the rectangular click target
+     * @aDestURLs is the list of target URLs for the menu
+     */
+    virtual void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
+    {
+        // NOP for most plotters.
+    }
+
     /**
      * Draw a marker (used for the drill map).
      */
diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h
index 7cfbd475c5..1df8d839ef 100644
--- a/include/plotters/plotters_pslike.h
+++ b/include/plotters/plotters_pslike.h
@@ -352,13 +352,14 @@ public:
                        KIFONT::FONT*               aFont = nullptr,
                        void*                       aData = nullptr ) override;
 
-    virtual void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override;
+    void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override;
+
+    void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs ) override;
 
     /**
      * PDF images are handles as inline, not XObject streams...
      */
-    virtual void PlotImage( const wxImage& aImage, const VECTOR2I& aPos,
-                            double aScaleFactor ) override;
+    void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) override;
 
 
 protected:
@@ -418,13 +419,15 @@ protected:
     std::vector<long> m_xrefTable;  ///< The PDF xref offset table
 
     ///< List of user-space page numbers for resolving internal hyperlinks
-    std::vector<wxString>                     m_pageNumbers;
+    std::vector<wxString>                                  m_pageNumbers;
 
     ///< List of loaded hyperlinks in current page
-    std::vector<std::pair<BOX2I, wxString>>   m_hyperlinksInPage;
+    std::vector<std::pair<BOX2I, wxString>>                m_hyperlinksInPage;
+    std::vector<std::pair<BOX2I, std::vector<wxString>>>   m_hyperlinkMenusInPage;
 
     ///< Handles for all the hyperlink objects that will be deferred
-    std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles;
+    std::map<int, std::pair<BOX2D, wxString>>              m_hyperlinkHandles;
+    std::map<int, std::pair<BOX2D, std::vector<wxString>>> m_hyperlinkMenuHandles;
 };