diff --git a/changes/changelog.d/1131.enhancement b/changes/changelog.d/1131.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..eb2f25b0b8525290442f474190fab2c507ed4499
--- /dev/null
+++ b/changes/changelog.d/1131.enhancement
@@ -0,0 +1 @@
+More consistent search UX on /albums, /artists, /radios and /playlists (#1131)
\ No newline at end of file
diff --git a/changes/changelog.d/407.enhancement b/changes/changelog.d/407.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..c21a34063e1b6b1930c7af15a5cfd5d72f218bb4
--- /dev/null
+++ b/changes/changelog.d/407.enhancement
@@ -0,0 +1 @@
+Confirm email without requiring the user to validate the form manually (#407)
\ No newline at end of file
diff --git a/front/src/EmbedFrame.vue b/front/src/EmbedFrame.vue
index a8dcce4fa01d9511831cf4cbf4936d97c42b8e58..afee7c82f927d8d79cd7fe84afe4c81cfc31eba8 100644
--- a/front/src/EmbedFrame.vue
+++ b/front/src/EmbedFrame.vue
@@ -89,7 +89,6 @@
           <tr
             :id="'queue-item-' + index"
             role="button"
-            tabindex="0"
             v-if="track.sources.length > 0"
             :key="index"
             :class="[{active: index === currentIndex}]"
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index de299a383f8e537ce18e8ca1e3d8f888cff370a0..1a46b5774d69a6f45bfd0cf74cf1b936c18ce98e 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -107,10 +107,12 @@ export default {
         return this.track.uploads && this.track.uploads.length > 0
       } else if (this.artist && this.artist.tracks_count) {
         return this.artist.tracks_count > 0
-      }  else if (this.artist && this.artist.albums) {
+      } else if (this.artist && this.artist.albums) {
         return this.artist.albums.filter((a) => {
           return a.is_playable === true
         }).length > 0
+      } else if (this.album) {
+        return this.album.is_playable
       } else if (this.tracks) {
         return this.tracks.filter((t) => {
           return t.uploads && t.uploads.length > 0
@@ -229,6 +231,7 @@ export default {
       jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
     },
     addNext (next) {
+      console.log('CLICKED')
       let self = this
       let wasEmpty = this.$store.state.queue.tracks.length === 0
       this.getPlayableTracks().then((tracks) => {
@@ -253,7 +256,6 @@ export default {
   },
   watch: {
     clicked () {
-
       let self = this
       this.$nextTick(() => {
         jQuery(this.$el).find('.ui.dropdown').dropdown({
diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue
index f0591a5385d36c59dd64fc556c5e5fe6ccb03abe..3f6eb0edb1a6720f4eed19b6a38a87d988f5391c 100644
--- a/front/src/components/auth/LoginForm.vue
+++ b/front/src/components/auth/LoginForm.vue
@@ -40,7 +40,7 @@
             <translate translate-context="*/Login/*/Verb">Reset your password</translate>
           </router-link>
         </label>
-        <password-input :index="2" required v-model="credentials.password" />
+        <password-input required v-model="credentials.password" />
 
       </div>
     </template>
diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue
index fe1a01a9ff688a599f6ca96af9061ecc12e675b5..2e4f227a59f879006d97846ae2d7a99002e85ec8 100644
--- a/front/src/components/forms/PasswordInput.vue
+++ b/front/src/components/forms/PasswordInput.vue
@@ -3,7 +3,6 @@
     <input
     required
     name="password"
-    :tabindex="index"
     :type="passwordInputType"
     @input="$emit('input', $event.target.value)"
     :value="value">
@@ -30,7 +29,7 @@ function copyStringToClipboard (str) {
 }
 
 export default {
-  props: ['value', 'index', 'defaultShow', 'copyButton'],
+  props: ['value', 'defaultShow', 'copyButton'],
   data () {
     return {
       showPassword: this.defaultShow || false,
diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue
index 071b133574ef69a0db0b26a1afdc85addb4b5f42..7b572e145616ba0435b96db10a9dc3b22c1f7b68 100644
--- a/front/src/components/library/AlbumBase.vue
+++ b/front/src/components/library/AlbumBase.vue
@@ -169,13 +169,16 @@ export default {
   methods: {
     async fetchData() {
       this.isLoading = true
+      let tracksResponse = axios.get(`tracks/`, {params: {ordering: 'disc_number,position', album: this.id, page_size: 100}})
       let albumResponse = await axios.get(`albums/${this.id}/`, {params: {refresh: 'true'}})
       let artistResponse = await axios.get(`artists/${albumResponse.data.artist.id}/`)
       this.artist = artistResponse.data
       if (this.artist.channel) {
         this.artist.channel.artist = this.artist
       }
-      this.object = backend.Album.clean(albumResponse.data)
+      tracksResponse = await tracksResponse
+      this.object = albumResponse.data
+      this.object.tracks = tracksResponse.data.results
       this.discs = this.object.tracks.reduce(groupByDisc, [])
       this.isLoading = false
 
diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue
index 60bafe245141b9b44370a97c54ba8ec4caadd744..647e37b475315a49c8dbaccea20cf22777807620 100644
--- a/front/src/components/library/Albums.vue
+++ b/front/src/components/library/Albums.vue
@@ -4,13 +4,18 @@
       <h2 class="ui header">
         <translate translate-context="Content/Album/Title">Browsing albums</translate>
       </h2>
-      <div :class="['ui', {'loading': isLoading}, 'form']">
+      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
         <div class="fields">
           <div class="field">
             <label>
               <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
             </label>
-            <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+            <div class="ui action input">
+              <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
+                <i class="search icon"></i>
+              </button>
+            </div>
           </div>
           <div class="field">
             <label><translate translate-context="*/*/*/Noun">Tags</translate></label>
@@ -40,7 +45,7 @@
             </select>
           </div>
         </div>
-      </div>
+      </form>
       <div class="ui hidden divider"></div>
       <div
         v-if="result"
@@ -144,7 +149,7 @@ export default {
     }
   },
   methods: {
-    updateQueryString: _.debounce(function() {
+    updateQueryString: function() {
       history.pushState(
         {},
         null,
@@ -157,8 +162,8 @@ export default {
           ordering: this.getOrderingAsString()
         }).toString()
       )
-    }, 500),
-    fetchData: _.debounce(function() {
+    },
+    fetchData: function() {
       var self = this
       this.isLoading = true
       let url = FETCH_URL
@@ -187,7 +192,7 @@ export default {
         self.result = null
         self.isLoading = false
       })
-    }, 500),
+    },
     selectPage: function(page) {
       this.page = page
     }
@@ -197,26 +202,6 @@ export default {
       this.updateQueryString()
       this.fetchData()
     },
-    paginateBy() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    ordering() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    orderingDirection() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    query() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    tags() {
-      this.updateQueryString()
-      this.fetchData()
-    },
     "$store.state.moderation.lastUpdate": function () {
       this.fetchData()
     }
diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue
index fefadd60b806b78e2bf4da6ef703b7951e2250f9..725d91bbec607a392aee7de1132143c74b3edc54 100644
--- a/front/src/components/library/ArtistBase.vue
+++ b/front/src/components/library/ArtistBase.vue
@@ -195,9 +195,7 @@ export default {
         self.nextAlbumsUrl = response.data.next
         self.totalAlbums = response.data.count
         let parsed = JSON.parse(JSON.stringify(response.data.results))
-        self.albums = parsed.map(album => {
-          return backend.Album.clean(album)
-        })
+        self.albums = parsed
 
       })
       await trackPromise
diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue
index 19e22089bf8f798456504c08e9ba11a6d8ce7da7..98d06e77630eb1998cd17fe818d780d73fff0d29 100644
--- a/front/src/components/library/Artists.vue
+++ b/front/src/components/library/Artists.vue
@@ -4,13 +4,18 @@
       <h2 class="ui header">
         <translate translate-context="Content/Artist/Title">Browsing artists</translate>
       </h2>
-      <div :class="['ui', {'loading': isLoading}, 'form']">
+      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
         <div class="fields">
           <div class="field">
             <label>
               <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
             </label>
-            <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+            <div class="ui action input">
+              <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
+                <i class="search icon"></i>
+              </button>
+            </div>
           </div>
           <div class="field">
             <label><translate translate-context="*/*/*/Noun">Tags</translate></label>
@@ -40,7 +45,7 @@
             </select>
           </div>
         </div>
-      </div>
+      </form>
       <div class="ui hidden divider"></div>
       <div v-if="result && result.results.length > 0" class="ui five app-cards cards">
         <div v-if="isLoading" class="ui inverted active dimmer">
@@ -134,7 +139,7 @@ export default {
     }
   },
   methods: {
-    updateQueryString: _.debounce(function() {
+    updateQueryString: function() {
       history.pushState(
         {},
         null,
@@ -147,8 +152,8 @@ export default {
           ordering: this.getOrderingAsString()
         }).toString()
       )
-    }, 500),
-    fetchData: _.debounce(function() {
+    },
+    fetchData: function() {
       var self = this
       this.isLoading = true
       let url = FETCH_URL
@@ -178,7 +183,7 @@ export default {
         self.result = null
         self.isLoading = false
       })
-    }, 500),
+    },
     selectPage: function(page) {
       this.page = page
     }
@@ -188,26 +193,6 @@ export default {
       this.updateQueryString()
       this.fetchData()
     },
-    paginateBy() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    ordering() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    orderingDirection() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    query() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    tags() {
-      this.updateQueryString()
-      this.fetchData()
-    },
     "$store.state.moderation.lastUpdate": function () {
       this.fetchData()
     }
diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue
index 1f5b121dcdbebf6b41052849b82b72e5c608b0c1..f96c25272366ee26c39a853b384d1cc4089f9108 100644
--- a/front/src/components/library/Radios.vue
+++ b/front/src/components/library/Radios.vue
@@ -25,11 +25,16 @@
         <translate translate-context="Content/Radio/Button.Label/Verb">Create your own radio</translate>
       </router-link>
       <div class="ui hidden divider"></div>
-      <div :class="['ui', {'loading': isLoading}, 'form']">
+      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
         <div class="fields">
           <div class="field">
             <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
-            <input name="search" type="text" v-model="query" :placeholder="labels.searchPlaceholder"/>
+            <div class="ui action input">
+              <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
+                <i class="search icon"></i>
+              </button>
+            </div>
           </div>
           <div class="field">
             <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
@@ -59,7 +64,7 @@
             </select>
           </div>
         </div>
-      </div>
+      </form>
       <div class="ui hidden divider"></div>
       <div v-if="result && !result.results.length > 0" class="ui placeholder segment">
         <div class="ui icon header">
@@ -157,7 +162,7 @@ export default {
     },
   },
   methods: {
-    updateQueryString: _.debounce(function() {
+    updateQueryString: function() {
       history.pushState(
         {},
         null,
@@ -169,8 +174,8 @@ export default {
           ordering: this.getOrderingAsString()
         }).toString()
       )
-    }, 500),
-    fetchData: _.debounce(function() {
+    },
+    fetchData: function() {
       var self = this
       this.isLoading = true
       let url = FETCH_URL
@@ -186,7 +191,7 @@ export default {
         self.result = response.data
         self.isLoading = false
       })
-    }, 500),
+    },
     selectPage: function(page) {
       this.page = page
     }
@@ -196,22 +201,6 @@ export default {
       this.updateQueryString()
       this.fetchData()
     },
-    paginateBy() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    ordering() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    orderingDirection() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    query() {
-      this.updateQueryString()
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/views/auth/EmailConfirm.vue b/front/src/views/auth/EmailConfirm.vue
index 26ccc5d70ced38e42a2c23b26bd4094c239bad3d..4e255ffb9c454f0eb2f7fdb55af4343e93c926d0 100644
--- a/front/src/views/auth/EmailConfirm.vue
+++ b/front/src/views/auth/EmailConfirm.vue
@@ -52,6 +52,11 @@ export default {
       }
     }
   },
+  mounted () {
+    if (this.key) {
+      this.submit()
+    }
+  },
   methods: {
     submit() {
       let self = this
diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue
index 237c2becc45e30e9d1073b837f0532a074de224a..83b29897f67b2d0b3c40b90033f585c96eb1a69e 100644
--- a/front/src/views/playlists/List.vue
+++ b/front/src/views/playlists/List.vue
@@ -2,17 +2,22 @@
   <main v-title="labels.playlists">
     <section class="ui vertical stripe segment">
       <h2 class="ui header"><translate translate-context="Content/Playlist/Title">Browsing playlists</translate></h2>
-      <div :class="['ui', {'loading': isLoading}, 'form']">
-        <template v-if="$store.state.auth.authenticated">
-          <button
-            @click="$store.commit('playlists/chooseTrack', null)"
-            class="ui basic success button"><translate translate-context="Content/Playlist/Button.Label/Verb">Manage your playlists</translate></button>
-          <div class="ui hidden divider"></div>
-        </template>
+      <template v-if="$store.state.auth.authenticated">
+        <button
+          @click="$store.commit('playlists/chooseTrack', null)"
+          class="ui basic success button"><translate translate-context="Content/Playlist/Button.Label/Verb">Manage your playlists</translate></button>
+        <div class="ui hidden divider"></div>
+      </template>
+      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
         <div class="fields">
           <div class="field">
             <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
-            <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+            <div class="ui action input">
+              <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
+              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
+                <i class="search icon"></i>
+              </button>
+            </div>
           </div>
           <div class="field">
             <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
@@ -38,7 +43,7 @@
             </select>
           </div>
         </div>
-      </div>
+      </form>
       <div class="ui hidden divider"></div>
       <playlist-card-list v-if="result && result.results.length > 0" :playlists="result.results"></playlist-card-list>
       <div v-else-if="result && !result.results.length > 0" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
@@ -124,7 +129,7 @@ export default {
     }
   },
   methods: {
-    updateQueryString: _.debounce(function() {
+    updateQueryString: function() {
       history.pushState(
         {},
         null,
@@ -136,8 +141,8 @@ export default {
           ordering: this.getOrderingAsString()
         }).toString()
       )
-    }, 250),
-    fetchData: _.debounce(function() {
+    },
+    fetchData: function() {
       var self = this
       this.isLoading = true
       let url = FETCH_URL
@@ -153,7 +158,7 @@ export default {
         self.result = response.data
         self.isLoading = false
       })
-    }, 500),
+    },
     selectPage: function(page) {
       this.page = page
     }
@@ -163,22 +168,6 @@ export default {
       this.updateQueryString()
       this.fetchData()
     },
-    paginateBy() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    ordering() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    orderingDirection() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    query() {
-      this.updateQueryString()
-      this.fetchData()
-    }
   }
 }
 </script>