diff --git a/changes/changelog.d/262.enhancement b/changes/changelog.d/262.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..cad67e939528317c60c989a1d6493c9fa5cbbc6f
--- /dev/null
+++ b/changes/changelog.d/262.enhancement
@@ -0,0 +1 @@
+Added feedback on shuffle button (#262)
diff --git a/front/src/App.vue b/front/src/App.vue
index 673f8386460ecba32737c129e3421adc06881f04..91cf29843429eb06e55d1aefd861b7eb3f81f1b4 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -1,6 +1,7 @@
 <template>
   <div id="app">
     <sidebar></sidebar>
+    <service-messages v-if="messages.length > 0" />
     <router-view :key="$route.fullPath"></router-view>
     <div class="ui fitted divider"></div>
     <div id="footer" class="ui vertical footer segment">
@@ -44,9 +45,11 @@
 <script>
 import axios from 'axios'
 import _ from 'lodash'
+import {mapState} from 'vuex'
 
 import Sidebar from '@/components/Sidebar'
 import Raven from '@/components/Raven'
+import ServiceMessages from '@/components/ServiceMessages'
 
 import PlaylistModal from '@/components/playlists/PlaylistModal'
 
@@ -55,7 +58,8 @@ export default {
   components: {
     Sidebar,
     Raven,
-    PlaylistModal
+    PlaylistModal,
+    ServiceMessages
   },
   data () {
     return {
@@ -80,6 +84,9 @@ export default {
     }
   },
   computed: {
+    ...mapState({
+      messages: state => state.ui.messages
+    }),
     version () {
       if (!this.nodeinfo) {
         return null
@@ -115,6 +122,14 @@ html, body {
   }
   transform: none !important;
 }
+.service-messages {
+  position: fixed;
+  bottom: 1em;
+  left: 1em;
+  @include media(">desktop") {
+    left: 350px;
+  }
+}
 .main-pusher {
   padding: 1.5rem 0;
 }
diff --git a/front/src/components/ServiceMessages.vue b/front/src/components/ServiceMessages.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0a3be99e5becb1f0efd13ec60d212a5f32233068
--- /dev/null
+++ b/front/src/components/ServiceMessages.vue
@@ -0,0 +1,82 @@
+<template>
+  <div class="service-messages">
+    <message v-for="message in displayedMessages" :key="String(message.date)" :class="['large', getLevel(message)]">
+      <p>{{ message.content }}</p>
+    </message>
+  </div>
+</template>
+
+<script>
+import {mapState} from 'vuex'
+
+export default {
+  data () {
+    return {
+      date: new Date(),
+      interval: null
+    }
+  },
+  created () {
+    this.setupInterval()
+  },
+  destroyed () {
+    if (this.interval) {
+      clearInterval(this.interval)
+    }
+  },
+  computed: {
+    ...mapState({
+      messages: state => state.ui.messages,
+      displayDuration: state => state.ui.messageDisplayDuration
+    }),
+    displayedMessages () {
+      let now = this.date
+      let interval = this.displayDuration
+      let toDisplay = this.messages.filter(m => {
+        return now - m.date <= interval
+      })
+      return toDisplay.slice(0, 3)
+    }
+  },
+  methods: {
+    setupInterval () {
+      if (this.interval) {
+        return
+      }
+      let self = this
+      this.interval = setInterval(() => {
+        if (self.displayedMessages.length === 0) {
+          clearInterval(self.interval)
+          this.interval = null
+        }
+        self.date = new Date()
+      }, 1000)
+    },
+    getLevel (message) {
+      return message.level || 'info'
+    }
+  },
+  watch: {
+    messages: {
+      handler (v) {
+        if (v.length > 0 && !this.interval) {
+          this.setupInterval()
+        }
+      },
+      deep: true
+    }
+  }
+}
+</script>
+
+<style>
+.service-messages {
+  z-index: 9999;
+  margin-left: 1em;
+  min-width: 20em;
+  max-width: 40em;
+}
+.service-messages .message:last-child {
+  margin-bottom: 0;
+}
+</style>
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index 28a8900841afc29fdefe844b3b8c473420f7809c..9777fa83ca2d52605e39d37219e25694e49b614c 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -124,19 +124,28 @@ export default {
     add () {
       let self = this
       this.getPlayableTracks().then((tracks) => {
-        self.$store.dispatch('queue/appendMany', {tracks: tracks})
+        self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => self.addMessage(tracks))
       })
     },
     addNext (next) {
       let self = this
       let wasEmpty = this.$store.state.queue.tracks.length === 0
       this.getPlayableTracks().then((tracks) => {
-        self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1})
+        self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1}).then(() => self.addMessage(tracks))
         let goNext = next && !wasEmpty
         if (goNext) {
           self.$store.dispatch('queue/next')
         }
       })
+    },
+    addMessage (tracks) {
+      if (tracks.length < 1) {
+        return
+      }
+      this.$store.commit('ui/addMessage', {
+        content: this.$t('{% tracks %} tracks were added to your queue.', {tracks: tracks.length}),
+        date: new Date()
+      })
     }
   }
 }
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index c475ec684a008ec46392361523eefc77ab38b0e6..3c922e14ad323d75413d969ba7c033add19e92e0 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -113,7 +113,8 @@
           :disabled="queue.tracks.length === 0"
           :title="$t('Shuffle your queue')"
           class="two wide column control">
-          <i @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
+          <div v-if="isShuffling" class="ui inline shuffling inverted small active loader"></div>
+          <i v-else @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
         </div>
         <div class="one wide column"></div>
         <div
@@ -158,6 +159,7 @@ export default {
   data () {
     let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
     return {
+      isShuffling: false,
       renderAudio: true,
       sliderVolume: this.volume,
       Track: Track,
@@ -173,9 +175,24 @@ export default {
     ...mapActions({
       togglePlay: 'player/togglePlay',
       clean: 'queue/clean',
-      shuffle: 'queue/shuffle',
       updateProgress: 'player/updateProgress'
     }),
+    shuffle () {
+      if (this.isShuffling) {
+        return
+      }
+      let self = this
+      this.isShuffling = true
+      setTimeout(() => {
+        self.$store.dispatch('queue/shuffle', () => {
+          self.isShuffling = false
+          self.$store.commit('ui/addMessage', {
+            content: self.$t('Queue shuffled!'),
+            date: new Date()
+          })
+        })
+      }, 100)
+    },
     next () {
       let self = this
       this.$store.dispatch('queue/next').then(() => {
@@ -402,5 +419,8 @@ export default {
 .ui.feed.icon {
   margin: 0;
 }
+.shuffling.loader.inline {
+  margin: 0;
+}
 
 </style>
diff --git a/front/src/components/common/Message.vue b/front/src/components/common/Message.vue
new file mode 100644
index 0000000000000000000000000000000000000000..772071db78c98881dab84405b2bc0903beff8106
--- /dev/null
+++ b/front/src/components/common/Message.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="ui message">
+    <div class="content">
+      <slot></slot>
+    </div>
+    <i class="close icon"></i>
+  </div>
+</template>
+<script>
+import $ from 'jquery'
+
+export default {
+  mounted () {
+    let self = this
+    $(this.$el).find('.close.icon').on('click', function () {
+      $(self.$el).transition('fade', 125)
+    })
+    $(this.$el).on('click', function () {
+      $(self.$el).transition('fade', 125)
+    })
+  }
+}
+</script>
+<style scoped>
+.ui.message .content {
+  padding-right: 0.5em;
+  cursor: pointer;
+}
+.ui.message .content :first-child {
+  margin-top: 0;
+}
+
+.ui.message .content :last-child {
+  margin-bottom: 0;
+}
+</style>
diff --git a/front/src/components/globals.js b/front/src/components/globals.js
index 79bbcf1b93a4a74ecf3c3b3d3d4e724870d7120c..4ad09f70425a987fd99e86f6e4277cd07d797605 100644
--- a/front/src/components/globals.js
+++ b/front/src/components/globals.js
@@ -12,4 +12,8 @@ import DangerousButton from '@/components/common/DangerousButton'
 
 Vue.component('dangerous-button', DangerousButton)
 
+import Message from '@/components/common/Message'
+
+Vue.component('message', Message)
+
 export default {}
diff --git a/front/src/store/queue.js b/front/src/store/queue.js
index 23e074a80c36fb849eb306ea59da68756a05282b..2d6c667b29ba5e87623f3cc60e7be31e0d5d3fc4 100644
--- a/front/src/store/queue.js
+++ b/front/src/store/queue.js
@@ -72,16 +72,20 @@ export default {
       }
     },
 
-    appendMany ({state, dispatch}, {tracks, index}) {
+    appendMany ({state, dispatch}, {tracks, index, callback}) {
       logger.default.info('Appending many tracks to the queue', tracks.map(e => { return e.title }))
       if (state.tracks.length === 0) {
         index = 0
       } else {
         index = index || state.tracks.length
       }
-      tracks.forEach((t) => {
-        dispatch('append', {track: t, index: index, skipPlay: true})
+      let total = tracks.length
+      tracks.forEach((t, i) => {
+        let p = dispatch('append', {track: t, index: index, skipPlay: true})
         index += 1
+        if (callback && i + 1 === total) {
+          p.then(callback)
+        }
       })
       dispatch('resume')
     },
@@ -148,13 +152,17 @@ export default {
       // so we replay automatically on next track append
       commit('ended', true)
     },
-    shuffle ({dispatch, commit, state}) {
+    shuffle ({dispatch, commit, state}, callback) {
       let toKeep = state.tracks.slice(0, state.currentIndex + 1)
       let toShuffle = state.tracks.slice(state.currentIndex + 1)
       let shuffled = toKeep.concat(_.shuffle(toShuffle))
       commit('player/currentTime', 0, {root: true})
       commit('tracks', [])
-      dispatch('appendMany', {tracks: shuffled})
+      let params = {tracks: shuffled}
+      if (callback) {
+        params.callback = callback
+      }
+      dispatch('appendMany', params)
     }
   }
 }
diff --git a/front/src/store/ui.js b/front/src/store/ui.js
index f0935e491bb1a48abc5f90d3482e3d5eab7ee9a5..be744afe51ad954a4bae722f9442a9d71ad85730 100644
--- a/front/src/store/ui.js
+++ b/front/src/store/ui.js
@@ -2,11 +2,20 @@
 export default {
   namespaced: true,
   state: {
-    lastDate: new Date()
+    lastDate: new Date(),
+    maxMessages: 100,
+    messageDisplayDuration: 10000,
+    messages: []
   },
   mutations: {
     computeLastDate: (state) => {
       state.lastDate = new Date()
+    },
+    addMessage (state, message) {
+      state.messages.push(message)
+      if (state.messages.length > state.maxMessages) {
+        state.messages.shift()
+      }
     }
   }
 }
diff --git a/front/test/unit/specs/store/ui.spec.js b/front/test/unit/specs/store/ui.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..adcfa87d8f34bd600f113fc4100c0c6318bc730e
--- /dev/null
+++ b/front/test/unit/specs/store/ui.spec.js
@@ -0,0 +1,18 @@
+import store from '@/store/ui'
+
+import { testAction } from '../../utils'
+
+describe('store/ui', () => {
+  describe('mutations', () => {
+    it('addMessage', () => {
+      const state = {maxMessages: 100, messages: []}
+      store.mutations.addMessage(state, 'hello')
+      expect(state.messages).to.deep.equal(['hello'])
+    })
+    it('addMessage', () => {
+      const state = {maxMessages: 1, messages: ['hello']}
+      store.mutations.addMessage(state, 'world')
+      expect(state.messages).to.deep.equal(['world'])
+    })
+  })
+})