Răsfoiți Sursa

Replace link has button role with button tag

# Change HTML tag to button

Replace the link tag with an HTML button to prevent some screen readers from having confusing announcements. By using the HTML button, users can use the Enter and Space keys to activate actions by default, instead of implementing them in JavaScript.

# Differentiate links and buttons visually

When activating the link element, the user may expect the web page to navigate to the URL and the page will refresh; when activating the button element, the user may expect the web page to still be on the same page, so that their current state, such as: input value, won't disappear.

Links and buttons should have different styles visually, so that users can't expect what will happen when they activate a link or a button.

I added the underline to the links, because that is the common pattern. Buttons have border and background color in a common pattern. But I think that will change the current layout drastically. So I added the focus, hover and active classes to the buttons instead.
Tân Î-sîn 2 ani în urmă
părinte
comite
ea58bac548

+ 4 - 6
internal/template/templates/common/feed_list.html

@@ -59,27 +59,25 @@
                             aria-describedby="feed-title-{{ .ID }}">{{ icon "edit" }}<span class="icon-label">{{ t "menu.edit_feed" }}</span></a>
                     </li>
                     <li class="item-meta-icons-remove">
-                        <a href="#"
-                            role="button"
+                        <button
                             aria-describedby="feed-title-{{ .ID }}"
                             data-confirm="true"
                             data-label-question="{{ t "confirm.question" }}"
                             data-label-yes="{{ t "confirm.yes" }}"
                             data-label-no="{{ t "confirm.no" }}"
                             data-label-loading="{{ t "confirm.loading" }}"
-                            data-url="{{ route "removeFeed" "feedID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></a>
+                            data-url="{{ route "removeFeed" "feedID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></button>
                     </li>
                     {{ if .UnreadCount }}
                       <li class="item-meta-icons-mark-as-read">
-                        <a href="#"
-                            role="button"
+                        <button
                             aria-describedby="feed-title-{{ .ID }}"
                             data-confirm="true"
                             data-label-question="{{ t "confirm.question" }}"
                             data-label-yes="{{ t "confirm.yes" }}"
                             data-label-no="{{ t "confirm.no" }}"
                             data-label-loading="{{ t "confirm.loading" }}"
-                            data-url="{{ route "markFeedAsRead" "feedID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></a>
+                            data-url="{{ route "markFeedAsRead" "feedID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></button>
                       </li>
                     {{ end }}
                 </ul>

+ 9 - 5
internal/template/templates/common/feed_menu.html

@@ -1,19 +1,23 @@
 {{ define "feed_menu" }}
 <nav aria-label="{{ t "page.feeds.title" }} {{ t "menu.title" }}"><ul>
     <li>
-        <a href="{{ route "feeds" }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
+        <a class="page-link" href="{{ route "feeds" }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
     </li>
     <li>
-        <a href="{{ route "addSubscription" }}">{{ icon "add-feed" }}{{ t "menu.add_feed" }}</a>
+        <a class="page-link" href="{{ route "addSubscription" }}">{{ icon "add-feed" }}{{ t "menu.add_feed" }}</a>
     </li>
     <li>
-        <a href="{{ route "export" }}">{{ icon "feed-export" }}{{ t "menu.export" }}</a>
+        <a class="page-link" href="{{ route "export" }}">{{ icon "feed-export" }}{{ t "menu.export" }}</a>
     </li>
     <li>
-        <a href="{{ route "import" }}">{{ icon "feed-import" }}{{ t "menu.import" }}</a>
+        <a class="page-link" href="{{ route "import" }}">{{ icon "feed-import" }}{{ t "menu.import" }}</a>
     </li>
     <li>
-        <a role="button" href="{{ route "refreshAllFeeds" }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
+        <form action="{{ route "refreshAllFeeds" }}" class="page-header-action-form">
+            <button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
+                {{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
+            </button>
+        </form>
     </li>
 </ul></nav>
 {{ end }}

+ 8 - 13
internal/template/templates/common/item_meta.html

@@ -17,8 +17,7 @@
     </ul>
     <ul class="item-meta-icons">
         <li class="item-meta-icons-read">
-            <a href="#"
-                role="button"
+            <button
                 aria-describedby="entry-title-{{ .entry.ID }}"
                 title="{{ t "entry.status.title" }}"
                 data-toggle-status="true"
@@ -26,11 +25,10 @@
                 data-label-read="{{ t "entry.status.read" }}"
                 data-label-unread="{{ t "entry.status.unread" }}"
                 data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
-                >{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.unread" }}{{ else }}{{ t "entry.status.read" }}{{ end }}</span></a>
+                >{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.unread" }}{{ else }}{{ t "entry.status.read" }}{{ end }}</span></button>
         </li>
         <li class="item-meta-icons-star">
-            <a href="#"
-                role="button"
+            <button
                 aria-describedby="entry-title-{{ .entry.ID }}"
                 data-toggle-bookmark="true"
                 data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
@@ -38,39 +36,36 @@
                 data-label-star="{{ t "entry.bookmark.toggle.on" }}"
                 data-label-unstar="{{ t "entry.bookmark.toggle.off" }}"
                 data-value="{{ if .entry.Starred }}star{{ else }}unstar{{ end }}"
-                >{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></a>
+                >{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></button>
         </li>
         {{ if .entry.ShareCode }}
             <li class="item-meta-icons-share">
                 <a href="{{ route "sharedEntry" "shareCode" .entry.ShareCode }}"
-                    role="button"
                     aria-describedby="entry-title-{{ .entry.ID }}"
                     title="{{ t "entry.shared_entry.title" }}"
                     target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.shared_entry.label" }}</span></a>
             </li>
             <li class="item-meta-icons-delete">
-                <a href="#"
-                    role="button"
+                <button
                     aria-describedby="entry-title-{{ .entry.ID }}"
                     data-confirm="true"
                     data-url="{{ route "unshareEntry" "entryID" .entry.ID }}"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
-                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></a>
+                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></button>
             </li>
         {{ end }}
         {{ if .hasSaveEntry }}
             <li>
-                <a href="#"
-                    role="button"
+                <button
                     aria-describedby="entry-title-{{ .entry.ID }}"
                     title="{{ t "entry.save.title" }}"
                     data-save-entry="true"
                     data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
                     data-label-loading="{{ t "entry.state.saving" }}"
                     data-label-done="{{ t "entry.save.completed" }}"
-                    >{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></a>
+                    >{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></button>
             </li>
         {{ end }}
         <li class="item-meta-icons-external-url">

+ 4 - 6
internal/template/templates/views/categories.html

@@ -55,28 +55,26 @@
                     </li>
                     {{ if eq (deRef .FeedCount) 0 }}
                     <li class="item-meta-icons-delete">
-                        <a href="#"
-                            role="button"
+                        <button
                             aria-describedby="category-title-{{ .ID }}"
                             data-confirm="true"
                             data-label-question="{{ t "confirm.question" }}"
                             data-label-yes="{{ t "confirm.yes" }}"
                             data-label-no="{{ t "confirm.no" }}"
                             data-label-loading="{{ t "confirm.loading" }}"
-                            data-url="{{ route "removeCategory" "categoryID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></a>
+                            data-url="{{ route "removeCategory" "categoryID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></button>
                     </li>
                     {{ end }}
                     {{ if gt (deRef .TotalUnread) 0 }}
                       <li class="item-meta-icons-mark-as-read">
-                        <a href="#"
-                            role="button"
+                        <button
                             aria-describedby="category-title-{{ .ID }}"
                             data-confirm="true"
                             data-label-question="{{ t "confirm.question" }}"
                             data-label-yes="{{ t "confirm.yes" }}"
                             data-label-no="{{ t "confirm.no" }}"
                             data-label-loading="{{ t "confirm.loading" }}"
-                            data-url="{{ route "markCategoryAsRead" "categoryID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></a>
+                            data-url="{{ route "markCategoryAsRead" "categoryID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></button>
                       </li>
                     {{ end }}
                 </ul>

+ 20 - 12
internal/template/templates/views/category_entries.html

@@ -17,40 +17,47 @@
         <ul>
             {{ if .entries }}
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
+                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
             </li>
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-url="{{ route "markCategoryAsRead" "categoryID" .category.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
+                    data-url="{{ route "markCategoryAsRead" "categoryID" .category.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
             </li>
             {{ end }}
             {{ if .showOnlyUnreadEntries }}
             <li>
-                <a href="{{ route "categoryEntriesAll" "categoryID" .category.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
+                <a class="page-link" href="{{ route "categoryEntriesAll" "categoryID" .category.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
             </li>
             {{ else }}
             <li>
-                <a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
+                <a class="page-link" href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
             </li>
             {{ end }}
             <li>
-                <a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
+                <a class="page-link" href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
             </li>
             <li>
-                <a role="button" href="{{ route "refreshCategoryEntriesPage" "categoryID" .category.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
+                <form
+                    action="{{ route "refreshCategoryEntriesPage" "categoryID" .category.ID }}"
+                    class="page-header-action-form"
+                >
+                    <button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
+                        {{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
+                    </button>
+                </form>
             </li>
         </ul>
     </nav>
@@ -101,13 +108,14 @@
         {{ if .entries }}
         <ul>
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
+                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
             </li>
         </ul>
         {{ end }}

+ 19 - 6
internal/template/templates/views/category_feeds.html

@@ -10,26 +10,39 @@
     <nav aria-label="{{ .category.Title }} {{ t "page.feeds.title" }} {{ t "menu.title" }}">
         <ul>
             <li>
-                <a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
+                <a class="page-link" href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
             </li>
             <li>
-                <a href="{{ route "editCategory" "categoryID" .category.ID }}">{{ icon "edit" }}{{ t "menu.edit_category" }}</a>
+                <a class="page-link" href="{{ route "editCategory" "categoryID" .category.ID }}">{{ icon "edit" }}{{ t "menu.edit_category" }}</a>
             </li>
             {{ if eq .total 0 }}
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
                     data-redirect-url="{{ route "categories" }}"
-                    data-url="{{ route "removeCategory" "categoryID" .category.ID }}">{{ icon "delete" }}{{ t "action.remove" }}</a>
+                    data-url="{{ route "removeCategory" "categoryID" .category.ID }}"
+                >
+                    {{ icon "delete" }}{{ t "action.remove" }}
+                </button>
             </li>
             {{ end }}
             <li>
-                <a role="button" href="{{ route "refreshCategoryFeedsPage" "categoryID" .category.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
+                <form
+                    class="page-header-action-form"
+                    action="{{ route "refreshCategoryFeedsPage" "categoryID" .category.ID }}"
+                >
+                    <button
+                        class="page-button"
+                        data-label-loading="{{ t "confirm.loading" }}"
+                    >
+                        {{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
+                    </button>
+                </form>
             </li>
         </ul>
     </nav>

+ 18 - 15
internal/template/templates/views/entry.html

@@ -10,8 +10,8 @@
         <div class="entry-actions">
             <ul>
                 <li>
-                    <a href="#"
-                        role="button"
+                    <button
+                        class="page-button"
                         title="{{ t "entry.status.title" }}"
                         data-toggle-status="true"
                         data-label-loading="{{ t "entry.state.saving" }}"
@@ -20,11 +20,11 @@
                         data-toast-unread="{{ t "entry.status.toast.unread" }}"
                         data-toast-read="{{ t "entry.status.toast.read" }}"
                         data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
-                        >{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.read" }}{{ else }}{{ t "entry.status.unread" }}{{ end }}</span></a>
+                        >{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.read" }}{{ else }}{{ t "entry.status.unread" }}{{ end }}</span></button>
                 </li>
                 <li>
-                    <a href="#"
-                        role="button"
+                    <button
+                        class="page-button"
                         data-toggle-bookmark="true"
                         data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
                         data-label-loading="{{ t "entry.state.saving" }}"
@@ -33,19 +33,19 @@
                         data-toast-star="{{ t "entry.bookmark.toast.on" }}"
                         data-toast-unstar="{{ t "entry.bookmark.toast.off" }}"
                         data-value="{{ if .entry.Starred }}star{{ else }}unstar{{ end }}"
-                        >{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></a>
+                        >{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></button>
                 </li>
                 {{ if .hasSaveEntry }}
                 <li>
-                    <a href="#"
-                        role="button"
+                    <button
+                        class="page-button"
                         title="{{ t "entry.save.title" }}"
                         data-save-entry="true"
                         data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
                         data-label-loading="{{ t "entry.state.saving" }}"
                         data-label-done="{{ t "entry.save.completed" }}"
                         data-toast-done="{{ t "entry.save.toast.completed" }}"
-                        >{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></a>
+                        >{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></button>
                 </li>
                 {{ end }}
                 {{ if .entry.ShareCode }}
@@ -56,18 +56,19 @@
                         target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.shared_entry.label" }}</span></a>
                 </li>
                 <li>
-                    <a href="#"
-                        role="button"
+                    <button
+                        class="page-button"
                         data-confirm="true"
                         data-url="{{ route "unshareEntry" "entryID" .entry.ID }}"
                         data-label-question="{{ t "confirm.question" }}"
                         data-label-yes="{{ t "confirm.yes" }}"
                         data-label-no="{{ t "confirm.no" }}"
-                        data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></a>
+                        data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></button>
                 </li>
                 {{ else }}
                 <li>
                     <a href="{{ route "shareEntry" "entryID" .entry.ID }}"
+                        class="page-link"
                         title="{{ t "entry.share.title" }}"
                         data-share-status="share"
                         target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.share.label" }}</span></a>
@@ -75,23 +76,25 @@
                 {{ end }}
                 <li>
                     <a href="{{ .entry.URL | safeURL  }}"
+                        class="page-link"
                         target="_blank"
                         rel="noopener noreferrer"
                         referrerpolicy="no-referrer"
                         data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
                 </li>
                 <li>
-                    <a href="#"
-                        role="button"
+                    <button
+                        class="page-button"
                         title="{{ t "entry.scraper.title" }}"
                         data-fetch-content-entry="true"
                         data-fetch-content-url="{{ route "fetchContent" "entryID" .entry.ID }}"
                         data-label-loading="{{ t "entry.state.loading" }}"
-                        >{{ icon "scraper" }}<span class="icon-label">{{ t "entry.scraper.label" }}</span></a>
+                        >{{ icon "scraper" }}<span class="icon-label">{{ t "entry.scraper.label" }}</span></button>
                 </li>
                 {{ if .entry.CommentsURL }}
                 <li>
                     <a href="{{ .entry.CommentsURL | safeURL }}"
+                        class="page-link"
                         title="{{ t "entry.comments.title" }}"
                         target="_blank"
                         rel="noopener noreferrer"

+ 18 - 13
internal/template/templates/views/feed_entries.html

@@ -17,48 +17,52 @@
         <ul>
             {{ if .entries }}
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
+                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
             </li>
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-url="{{ route "markFeedAsRead" "feedID" .feed.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
+                    data-url="{{ route "markFeedAsRead" "feedID" .feed.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
             </li>
             {{ end }}
             {{ if .showOnlyUnreadEntries }}
             <li>
-                <a href="{{ route "feedEntriesAll" "feedID" .feed.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
+                <a class="page-link" href="{{ route "feedEntriesAll" "feedID" .feed.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
             </li>
             {{ else }}
             <li>
-                <a href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
+                <a class="page-link" href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
             </li>
             {{ end }}
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-label-question="{{ t "confirm.question.refresh" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
                     data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
-                    data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
+                    data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</button>
             </li>
             <li>
-                <a href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>
+                <a class="page-link" href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>
             </li>
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-action="remove-feed"
                     data-label-question="{{ t "confirm.question" }}"
@@ -66,7 +70,7 @@
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
                     data-url="{{ route "removeFeed" "feedID" .feed.ID }}"
-                    data-redirect-url="{{ route "feeds" }}">{{ icon "delete" }}{{ t "action.remove_feed" }}</a>
+                    data-redirect-url="{{ route "feeds" }}">{{ icon "delete" }}{{ t "action.remove_feed" }}</button>
             </li>
         </ul>
     </nav>
@@ -131,13 +135,14 @@
         {{ if .entries }}
         <ul>
             <li>
-                <a href="#"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
                     data-label-loading="{{ t "confirm.loading" }}"
-                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
+                    data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
             </li>
         </ul>
         {{ end }}

+ 4 - 4
internal/template/templates/views/history_entries.html

@@ -11,18 +11,18 @@
         <ul>
             {{ if .entries }}
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-url="{{ route "flushHistory" }}"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
-                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</a>
+                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</button>
             </li>
             {{ end }}
             <li>
-                <a href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
+                <a class="page-link" href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
             </li>
         </ul>
     </nav>

+ 1 - 1
internal/template/templates/views/login.html

@@ -43,6 +43,6 @@
     {{ end }}
 </section>
 <footer id="prompt-home-screen">
-    <a href="#" id="btn-add-to-home-screen" role="button">{{ icon "home" }}<span class="icon-label">{{ t "action.home_screen" }}</span></a>
+    <button id="btn-add-to-home-screen">{{ icon "home" }}<span class="icon-label">{{ t "action.home_screen" }}</span></button>
 </footer>
 {{ end }}

+ 4 - 4
internal/template/templates/views/shared_entries.html

@@ -11,17 +11,17 @@
     <nav aria-label="{{ t "page.shared_entries.title" }} {{ t "menu.title" }}">
         <ul>
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-url="{{ route "flushHistory" }}"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
-                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</a>
+                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</button>
             </li>
             <li>
-                <a href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
+                <a class="page-link" href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
             </li>
         </ul>
     </nav>

+ 9 - 9
internal/template/templates/views/unread_entries.html

@@ -11,25 +11,25 @@
     <nav aria-label="{{ t "page.unread.title" }} {{ t "menu.title" }}">
         <ul>
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-show-only-unread="1"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
-                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
+                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
             </li>
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-confirm="true"
                     data-url="{{ route "markAllAsRead" }}"
                     data-redirect-url="{{ route "unread" }}"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
                     data-label-no="{{ t "confirm.no" }}"
-                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
+                    data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
             </li>
         </ul>
     </nav>
@@ -75,8 +75,8 @@
         {{ if .entries }}
         <ul>
             <li>
-                <a href="#"
-                    role="button"
+                <button
+                    class="page-button"
                     data-action="markPageAsRead"
                     data-label-question="{{ t "confirm.question" }}"
                     data-label-yes="{{ t "confirm.yes" }}"
@@ -84,7 +84,7 @@
                     data-label-loading="{{ t "confirm.loading" }}"
                 >
                     {{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}
-                </a>
+                </button>
             </li>
         </ul>
         {{ end }}

+ 45 - 5
internal/ui/static/css/common.css

@@ -180,6 +180,26 @@ a:hover {
     padding-bottom: 2px;
 }
 
+.page-header-action-form {
+    display: inline-flex;
+}
+
+:is(.page-button, .page-link) {
+    color: var(--link-color);
+    border: none;
+    background-color: transparent;
+    font-size: 1rem;
+    cursor: pointer;
+
+    &:is(hover, :focus) {
+        color: var(--link-hover-color);
+    }
+}
+
+.page-button:active {
+    translate: 1px 1px;
+}
+
 /* Logo */
 .logo {
     text-align: center;
@@ -850,8 +870,7 @@ template {
     text-decoration: none;
 }
 
-.item-meta a:hover,
-.item-meta a:focus {
+.item-meta :is(a:is(:focus, :hover), button:is(:focus, :hover)) {
     color: #333;
 }
 
@@ -881,6 +900,23 @@ template {
     margin-right: 0;
 }
 
+.item-meta-icons li > :is(a, button) {
+    color: #777;
+    text-decoration: none;
+    font-size: 0.8rem;
+    border: none;
+    background-color: transparent;
+    cursor: pointer;
+}
+
+.item-meta-icons a span {
+    text-decoration: underline;
+}
+
+.item-meta-icons button:active {
+    translate: 1px 1px;
+}
+
 .items {
     overflow-x: hidden;
     touch-action: pan-y;
@@ -967,8 +1003,8 @@ article.category-has-unread {
     margin-bottom: 20px;
 }
 
-.entry-actions a {
-    text-decoration: none;
+.entry-actions a span {
+    text-decoration: underline;
 }
 
 .entry-actions li {
@@ -1198,8 +1234,12 @@ details.entry-enclosures {
     color: #ed2d04;
 }
 
-.confirm a {
+.confirm button {
     color: #ed2d04;
+    border: none;
+    background-color: transparent;
+    cursor: pointer;
+    font-size: inherit;
 }
 
 .loading {

+ 16 - 18
internal/ui/static/js/app.js

@@ -135,7 +135,7 @@ function markPageAsRead() {
         updateEntriesStatus(entryIDs, "read", () => {
             // Make sure the Ajax request reach the server before we reload the page.
 
-            let element = document.querySelector("a[data-action=markPageAsRead]");
+            let element = document.querySelector(":is(a, button)[data-action=markPageAsRead]");
             let showOnlyUnread = false;
             if (element) {
                 showOnlyUnread = element.dataset.showOnlyUnread || false;
@@ -161,7 +161,7 @@ function handleEntryStatus(item, element, setToRead) {
     let toasting = !element;
     let currentEntry = findEntry(element);
     if (currentEntry) {
-        if (!setToRead || currentEntry.querySelector("a[data-toggle-status]").dataset.value == "unread") {
+        if (!setToRead || currentEntry.querySelector(":is(a, button)[data-toggle-status]").dataset.value == "unread") {
             toggleEntryStatus(currentEntry, toasting);
         }
         if (isListView() && currentEntry.classList.contains('current-item')) {
@@ -180,7 +180,7 @@ function handleEntryStatus(item, element, setToRead) {
 // Change the entry status to the opposite value.
 function toggleEntryStatus(element, toasting) {
     let entryID = parseInt(element.dataset.id, 10);
-    let link = element.querySelector("a[data-toggle-status]");
+    let link = element.querySelector(":is(a, button)[data-toggle-status]");
 
     let currentStatus = link.dataset.value;
     let newStatus = currentStatus === "read" ? "unread" : "read";
@@ -259,7 +259,7 @@ function handleSaveEntry(element) {
     let toasting = !element;
     let currentEntry = findEntry(element);
     if (currentEntry) {
-        saveEntry(currentEntry.querySelector("a[data-save-entry]"), toasting);
+        saveEntry(currentEntry.querySelector(":is(a, button)[data-save-entry]"), toasting);
     }
 }
 
@@ -299,7 +299,7 @@ function handleBookmark(element) {
 
 // Send the Ajax request and change the icon when bookmarking an entry.
 function toggleBookmark(parentElement, toasting) {
-    let element = parentElement.querySelector("a[data-toggle-bookmark]");
+    let element = parentElement.querySelector(":is(a, button)[data-toggle-bookmark]");
     if (!element) {
         return;
     }
@@ -340,7 +340,7 @@ function handleFetchOriginalContent() {
         return;
     }
 
-    let element = document.querySelector("a[data-fetch-content-entry]");
+    let element = document.querySelector(":is(a, button)[data-fetch-content-entry]");
     if (!element) {
         return;
     }
@@ -376,13 +376,13 @@ function openOriginalLink(openLinkInCurrentTab) {
         return;
     }
 
-    let currentItemOriginalLink = document.querySelector(".current-item a[data-original-link]");
+    let currentItemOriginalLink = document.querySelector(".current-item :is(a, button)[data-original-link]");
     if (currentItemOriginalLink !== null) {
         DomHelper.openNewTab(currentItemOriginalLink.getAttribute("href"));
 
         let currentItem = document.querySelector(".current-item");
         // If we are not on the list of starred items, move to the next item
-        if (document.location.href != document.querySelector('a[data-page=starred]').href) {
+        if (document.location.href != document.querySelector(':is(a, button)[data-page=starred]').href) {
             goToListItem(1);
         }
         markEntryAsRead(currentItem);
@@ -391,7 +391,7 @@ function openOriginalLink(openLinkInCurrentTab) {
 
 function openCommentLink(openLinkInCurrentTab) {
     if (!isListView()) {
-        let entryLink = document.querySelector("a[data-comments-link]");
+        let entryLink = document.querySelector(":is(a, button)[data-comments-link]");
         if (entryLink !== null) {
             if (openLinkInCurrentTab) {
                 window.location.href = entryLink.getAttribute("href");
@@ -401,7 +401,7 @@ function openCommentLink(openLinkInCurrentTab) {
             return;
         }
     } else {
-        let currentItemCommentsLink = document.querySelector(".current-item a[data-comments-link]");
+        let currentItemCommentsLink = document.querySelector(".current-item :is(a, button)[data-comments-link]");
         if (currentItemCommentsLink !== null) {
             DomHelper.openNewTab(currentItemCommentsLink.getAttribute("href"));
         }
@@ -437,7 +437,7 @@ function unsubscribeFromFeed() {
  * @param {boolean} fallbackSelf Refresh actual page if the page is not found.
  */
 function goToPage(page, fallbackSelf) {
-    let element = document.querySelector("a[data-page=" + page + "]");
+    let element = document.querySelector(":is(a, button)[data-page=" + page + "]");
 
     if (element) {
         document.location.href = element.href;
@@ -477,7 +477,7 @@ function goToFeed() {
             window.location.href = feedAnchor.href;
         }
     } else {
-        let currentItemFeed = document.querySelector(".current-item a[data-feed-link]");
+        let currentItemFeed = document.querySelector(".current-item :is(a, button)[data-feed-link]");
         if (currentItemFeed !== null) {
             window.location.href = currentItemFeed.getAttribute("href");
         }
@@ -574,7 +574,7 @@ function findEntry(element) {
 }
 
 function handleConfirmationMessage(linkElement, callback) {
-    if (linkElement.tagName != 'A') {
+    if (linkElement.tagName != 'A' && linkElement.tagName != "BUTTON") {
         linkElement = linkElement.parentNode;
     }
 
@@ -592,8 +592,7 @@ function handleConfirmationMessage(linkElement, callback) {
         containerElement.appendChild(loadingElement);
     }
 
-    let yesElement = document.createElement("a");
-    yesElement.href = "#";
+    let yesElement = document.createElement("button");
     yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
     yesElement.onclick = (event) => {
         event.preventDefault();
@@ -603,8 +602,7 @@ function handleConfirmationMessage(linkElement, callback) {
         callback(linkElement.dataset.url, linkElement.dataset.redirectUrl);
     };
 
-    let noElement = document.createElement("a");
-    noElement.href = "#";
+    let noElement = document.createElement("button");
     noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo));
     noElement.onclick = (event) => {
         event.preventDefault();
@@ -677,7 +675,7 @@ function handlePlayerProgressionSave(playerElement) {
  * handle new share entires and already shared entries
  */
 function handleShare() {
-    let link = document.querySelector('a[data-share-status]');
+    let link = document.querySelector(':is(a, button)[data-share-status]');
     let title = document.querySelector("body > main > section > header > h1 > a");
     if (link.dataset.shareStatus === "shared") {
         checkShareAPI(title, link.href);

+ 8 - 8
internal/ui/static/js/bootstrap.js

@@ -79,14 +79,14 @@ document.addEventListener("DOMContentLoaded", () => {
         }
     }
 
-    onClick("a[data-save-entry]", (event) => handleSaveEntry(event.target));
-    onClick("a[data-toggle-bookmark]", (event) => handleBookmark(event.target));
-    onClick("a[data-fetch-content-entry]", () => handleFetchOriginalContent());
-    onClick("a[data-share-status]", () => handleShare());
-    onClick("a[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, () => markPageAsRead()));
-    onClick("a[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
-
-    onClick("a[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
+    onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target));
+    onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
+    onClick(":is(a, button)[data-fetch-content-entry]", () => handleFetchOriginalContent());
+    onClick(":is(a, button)[data-share-status]", () => handleShare());
+    onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, () => markPageAsRead()));
+    onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
+
+    onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
         let request = new RequestBuilder(url);
 
         request.withCallback((response) => {