diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue
index ec2a5d07f5d4fa8812cd2b2d8da1dedcca000a10..5b6fe4777530ebbe7898f2d45712b614b841f0a2 100644
--- a/src/components/AppHeader.vue
+++ b/src/components/AppHeader.vue
@@ -172,7 +172,7 @@
               data-position="bottom right"
             >
               <div class="crossed-out">
-                <i class="wifi icon"/>
+                <i class="wifi icon" />
               </div>
             </span>
           </div>
@@ -228,21 +228,21 @@
           </div>
         </div>
       </div>
-      <MessageInfo />
+      <MessageInfoList />
     </div>
   </div>
 </template>
 
 <script>
 import { mapState } from 'vuex';
-import MessageInfo from '@/components/MessageInfo';
+import MessageInfoList from '@/components/MessageInfoList';
 
 export default {
 
   name: 'AppHeader',
 
   components: {
-    MessageInfo
+    MessageInfoList
   },
 
   data() {
diff --git a/src/components/Feature/Edit/FeatureExtraForm.vue b/src/components/Feature/Edit/FeatureExtraForm.vue
index 36f4130b18a0fb6a3503c35eb256e1ef209d3bd7..6e671d42c2baa854b1651e76c63b6e01587cca0c 100644
--- a/src/components/Feature/Edit/FeatureExtraForm.vue
+++ b/src/components/Feature/Edit/FeatureExtraForm.vue
@@ -140,7 +140,7 @@ export default {
     },
 
     displayLabels() {
-      return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement';
+      return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement' || this.$route.name === 'editer-attribut-signalement';
     }
   },
 
diff --git a/src/components/Feature/FeatureListMassToggle.vue b/src/components/Feature/FeatureListMassToggle.vue
deleted file mode 100644
index ff4b1da6ff6616933727a93e6595b10238a7bfd8..0000000000000000000000000000000000000000
--- a/src/components/Feature/FeatureListMassToggle.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<template>
-  <div
-    class="switch-buttons pointer"
-    :data-tooltip="`Passer en mode ${massMode === 'modify' ? 'suppression':'édition'}`"
-    @click="switchMode"
-  >
-    <div>
-      <i
-        :class="['icon pencil', {disabled: massMode !== 'modify'}]"
-        aria-hidden="true"
-      />
-    </div>
-    <span class="grey">|&nbsp;</span>
-    <div>
-      <i
-        :class="['icon trash', {disabled: massMode !== 'delete'}]"
-        aria-hidden="true"
-      />
-    </div>
-  </div>
-</template>
-
-<script>
-import { mapMutations, mapState } from 'vuex';
-
-export default {
-  name: 'FeatureListMassToggle',
-
-  computed: {
-    ...mapState('feature', ['massMode'])
-  },
-
-  methods: {
-    ...mapMutations('feature', [
-      'TOGGLE_MASS_MODE',
-      'UPDATE_CHECKED_FEATURES',
-      'UPDATE_CLICKED_FEATURES']),
-
-    switchMode() {
-      this.TOGGLE_MASS_MODE(this.massMode === 'modify' ? 'delete' : 'modify');
-      this.UPDATE_CLICKED_FEATURES([]);
-      this.UPDATE_CHECKED_FEATURES([]);
-    }
-  },
-};
-</script>
-
-<style scoped>
-.switch-buttons {
-  display: flex;
-  justify-content: center;
-  align-items: baseline;
-}
-
-.grey {
-  color: #bbbbbb;
-}
-
-</style>
diff --git a/src/components/MessageInfo.vue b/src/components/MessageInfo.vue
index 3996dc9de95d967afa14deee2d063ddf3dfeddf0..4fe49097eaeefdfd6129215601c6921ce2e80111 100644
--- a/src/components/MessageInfo.vue
+++ b/src/components/MessageInfo.vue
@@ -1,36 +1,30 @@
 <template>
-  <transition name="fadeDownUp">
-    <div
-      v-if="messages && messages.length > 0"
-      class="row over-content"
-    >
-      <div class="fourteen wide column">
-        <div
-          v-for="(message, index) in messages"
-          :key="'message-' + index"
-          :class="['ui', message.level ? message.level : 'info', 'message']"
-        >
+  <li
+    :ref="'message-' + message.counter"
+    :class="['list-container', { show }]"
+  >
+    <div :class="['list-item', { show}]">
+      <div :class="['ui', message.level ? message.level : 'info', 'message']">
+        <i
+          class="close icon"
+          aria-hidden="true"
+          @click="removeListItem"
+        />
+        <div class="header">
           <i
-            class="close icon"
+            class="info circle icon"
             aria-hidden="true"
-            @click="DISCARD_MESSAGE(message)"
           />
-          <div class="header">
-            <i
-              class="info circle icon"
-              aria-hidden="true"
-            />
-            Informations
-          </div>
-          <ul class="list">
-            {{
-              message.comment
-            }}
-          </ul>
+          Informations
         </div>
+        <ul class="list">
+          {{
+            message.comment
+          }}
+        </ul>
       </div>
     </div>
-  </transition>
+  </li>
 </template>
 
 <script>
@@ -39,54 +33,100 @@ import { mapState, mapMutations } from 'vuex';
 export default {
   name: 'MessageInfo',
 
+  props: {
+    message: {
+      type: Object,
+      default: () => {},
+    },
+  },
+
+  data() {
+    return {
+      listMessages: [],
+      show: false,
+    };
+  },
+
   computed: {
     ...mapState(['messages']),
   },
 
+  mounted() {
+    setTimeout(() => {
+      this.show = true;
+    }, 15);
+  },
+
   methods: {
     ...mapMutations(['DISCARD_MESSAGE']),
+
+    removeListItem(){
+      const container = this.$refs['message-' + this.message.counter];
+      container.ontransitionend = () => {
+        this.DISCARD_MESSAGE(this.message.counter);
+      };
+      this.show = false;
+    },
   },
   
 };
 </script>
 
-<style>
-.row.over-content {
-  position: absolute; /* to display message info over page content */
-  z-index: 99;
-  opacity: 0.95;
-  width: calc(100% - 4em); /* 4em is #content left + right paddings */
-  top: calc(61px + 1em); /* 61px is #app-header height */
-  right: 2em; /* 2em is #content left paddings */
+<style scoped>
+.list-container{
+    list-style: none;
+    width: 100%;
+    height: 0;
+    position: relative;
+    cursor: pointer;
+    overflow: hidden;
+    transition: all 0.6s ease-out;
 }
-
-.fadeDownUp-enter-active {
-  animation: fadeInDown .5s;
+.list-container.show{
+  height: 6em;
 }
-.fadeDownUp-leave-active {
-  animation: fadeOutUp .5s;
+.list-container.show:not(:first-child){
+  margin-top: 10px;
 }
-@keyframes fadeOutUp {
-  0% {
-    opacity: 1;
-  }
-
-  100% {
+.list-container .list-item{
+    padding: .5rem 0;
+    width: 100%;
+    position: absolute;
     opacity: 0;
-    transform: translate3d(0, -100%, 0);
-  }
+    top: 0;
+    left: 0;
+    transition: all 0.6s ease-out;
+}
+.list-container .list-item.show{
+    opacity: 1;
 }
 
-@keyframes fadeInDown {
-  from {
-    opacity: 0;
-    transform: translate3d(0, -100%, 0);
-  }
+ul.list{
+  overflow: scroll;
+  height: 2.2em;
+  margin-bottom: .5em !important;
+}
 
-  to {
-    opacity: 1;
-    transform: translate3d(0, 0, 0);
-  }
+.ui.message {
+  overflow: hidden;
+  padding-bottom: 0 !important;
+}
+.ui.message::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 1em;
+  right: 0;
+  width: calc(100% - 2em);
+}
+.ui.info.message::after {
+  box-shadow: 0px -8px 5px 3px rgb(248, 255, 255);
+}
+.ui.positive.message::after {
+  box-shadow: 0px -8px 5px 3px rgb(248, 255, 255);
+}
+.ui.negative.message::after {
+  box-shadow: 0px -8px 5px 3px rgb(248, 255, 255);
 }
 
 .ui.message > .close.icon {
diff --git a/src/components/MessageInfoList.vue b/src/components/MessageInfoList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5974a28d54314d12975ceb58e0cf6eee917aa603
--- /dev/null
+++ b/src/components/MessageInfoList.vue
@@ -0,0 +1,50 @@
+<template>
+  <div
+    v-if="messages && messages.length > 0"
+    class="row over-content"
+  >
+    <div class="fourteen wide column">
+      <ul
+        class="message-list"
+        aria-live="assertive"
+      >
+        <MessageInfo
+          v-for="message in messages"
+          :key="'message-' + message.counter"
+          :message="message"
+        />
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex';
+import MessageInfo from '@/components/MessageInfo';
+
+export default {
+  name: 'MessageInfoList',
+  
+  components: {
+    MessageInfo,
+  },
+  computed: {
+    ...mapState(['messages']),
+  },
+};
+</script>
+
+<style scoped>
+
+.row.over-content {
+  position: absolute; /* to display message info over page content */
+  z-index: 99;
+  opacity: 0.95;
+  width: calc(100% - 4em); /* 4em is #content left + right paddings */
+  top: calc(61px + 1em); /* 61px is #app-header height */
+  right: 2em; /* 2em is #content left paddings */
+}
+.message-list{
+    list-style: none;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
index cc4b12e12e55c74c05befd20f97c08c816682d51..a522fc895075e010c2c9e902d4cc6e395bf1c636 100644
--- a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
+++ b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
@@ -1,8 +1,53 @@
 <template>
   <div>
-    <div class="table-mobile-buttons left-align">
-      <FeatureListMassToggle v-if="isOnline" />
+    <div class="ui form">
+      <div
+        v-if="isOnline"
+        class="inline fields"
+      >
+        <label
+          data-tooltip="Choisir un type de sélection de signalements pour effectuer une action"
+          data-position="bottom left"
+        >Mode de sélection :</label>
+        <div class="field">
+          <div class="ui radio checkbox">
+            <input
+              id="edit-status"
+              v-model="mode"
+              type="radio"
+              name="mode"
+              value="edit-status"
+            >
+            <label for="edit-status">Édition de statut</label>
+          </div>
+        </div>
+        <div class="field">
+          <div class="ui radio checkbox">
+            <input
+              id="edit-attributes"
+              v-model="mode"
+              type="radio"
+              name="mode"
+              value="edit-attributes"
+            >
+            <label for="edit-attributes">Édition d'attribut</label>
+          </div>
+        </div>
+        <div class="field">
+          <div class="ui radio checkbox">
+            <input
+              id="delete-features"
+              v-model="mode"
+              type="radio"
+              name="mode"
+              value="delete-features"
+            >
+            <label for="delete-features">Suppression de signalement</label>
+          </div>
+        </div>
+      </div>
     </div>
+
     <div
       data-tab="list"
       class="dataTables_wrapper no-footer"
@@ -19,7 +64,7 @@
               scope="col"
               class="dt-center"
             >
-              <FeatureListMassToggle />
+              Sélection
             </th>
 
             <th
@@ -356,7 +401,6 @@
 
 <script>
 import { mapState, mapGetters, mapMutations } from 'vuex';
-import FeatureListMassToggle from '@/components/Feature/FeatureListMassToggle';
 import { formatStringDate } from '@/utils';
 
 export default {
@@ -368,10 +412,13 @@ export default {
     },
   },
 
-  components: {
-    FeatureListMassToggle,
+  beforeRouteLeave (to, from, next) {
+    if (to.name !== 'editer-attribut-signalement') {
+      this.UPDATE_CHECKED_FEATURES([]); // empty if not needed anymore
+    }
+    next(); // continue navigation
   },
-
+  
   props: {
     paginatedFeatures: {
       type: Array,
@@ -400,7 +447,11 @@ export default {
     queryparams: {
       type: Object,
       default: null,
-    }
+    },
+    editAttributesFeatureType: {
+      type: String,
+      default: null,
+    },
   },
 
   computed: {
@@ -413,6 +464,17 @@ export default {
     ...mapState('projects', ['project']),
     ...mapState('feature', ['clickedFeatures', 'massMode']),
 
+    mode: {
+      get() {
+        return this.massMode;
+      },
+      set(newMode) {
+        this.TOGGLE_MASS_MODE(newMode);
+        this.UPDATE_CLICKED_FEATURES([]);
+        this.UPDATE_CHECKED_FEATURES([]);
+      },
+    },
+
     userStatus() {
       return this.USER_LEVEL_PROJECTS[this.$route.params.slug];
     },
@@ -457,17 +519,21 @@ export default {
     },
   },
 
-  destroyed() {
-    this.UPDATE_CHECKED_FEATURES([]);
-  },
-
   methods: {
     ...mapMutations('feature', [
       'UPDATE_CLICKED_FEATURES',
       'UPDATE_CHECKED_FEATURES',
+      'TOGGLE_MASS_MODE',
     ]),
 
     storeClickedFeature(feature) {
+      if (this.massMode === 'edit-attributes') { // if modifying attributes
+        if (this.checkedFeatures.length === 0) { // store feature type slug to restrict selection for next selected features
+          this.$emit('update:editAttributesFeatureType', feature.feature_type.slug);
+        } else if (this.checkedFeatures.length === 1 && this.checkedFeatures[0] === feature.feature_id) {
+          this.$emit('update:editAttributesFeatureType', null); // delete feature type slug if last checkedFeatures is unselected, to allow other types selection
+        }
+      }
       this.UPDATE_CLICKED_FEATURES([
         ...this.clickedFeatures,
         { feature_id: feature.feature_id, feature_type: feature.feature_type.slug }
@@ -490,7 +556,11 @@ export default {
         Contributeur : ['draft', 'pending', 'published'],
       };
 
-      if (this.user.is_superuser) {
+      if (this.checkedFeatures.length > 0 && // check if selection should be restricted to a specific feature type, for attributes modification
+        feature.feature_type.slug !== this.editAttributesFeatureType &&
+        this.massMode === 'edit-attributes') {
+        return false;
+      } else if (this.user.is_superuser) {
         return true;
       } else if (this.userStatus === 'Contributeur' && feature.display_creator !== `${this.user.first_name} ${this.user.last_name}`) {
         return false;
@@ -502,20 +572,13 @@ export default {
     },
 
     checkRights(feature) {
-      switch (this.massMode) {
-      case 'modify':
+      if (this.massMode.includes('edit')) {
         return this.canEditFeature(feature);
-      case 'delete':
+      } else if (this.massMode === 'delete-features') {
         return this.canDeleteFeature(feature);
       }
     },
 
-    switchMode() {
-      this.$emit('update:mode', this.mode === 'modify' ? 'delete' : 'modify');
-      this.UPDATE_CLICKED_FEATURES([]);
-      this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []);
-    },
-
     isSortedAsc(column) {
       return this.sort.column === column && this.sort.ascending;
     },
@@ -641,8 +704,9 @@ i.icon.sort:not(.down):not(.up) {
   margin-bottom: 1em;
 }
 
-#table-features .disabled {
-  opacity: .5;
+/* increase contrast between available checkboxes and disabled ones */
+#table-features .ui.disabled.checkbox label::before {
+  background-color: #fbf5f5;;  
 }
 
 @media only screen and (min-width: 761px) {
diff --git a/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue b/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
index 2c85be503f2d7d6eddc3857c87ebe5a6f50254b1..5d2c39aac1ef890d6bbb17b0d2e572ff9b2a0493 100644
--- a/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
+++ b/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
@@ -86,11 +86,11 @@
               </div>
             </div>
             <div
-              v-if="checkedFeatures.length > 0 && massMode === 'modify' && isOnline"
+              v-if="checkedFeatures.length > 0 && massMode.includes('edit') && isOnline"
               class="ui dropdown button compact button-hover-green tiny-margin-left"
-              data-tooltip="Modifier le statut des Signalements"
+              :data-tooltip="`Modifier le${massMode.includes('status') ? ' statut' : 's attributs'} des signalements`"
               data-position="bottom right"
-              @click="toggleModifyStatus"
+              @click="editFeatures"
             >
               <i
                 class="pencil fitted icon"
@@ -109,7 +109,7 @@
                     v-for="status in availableStatus"
                     :key="status.value"
                     class="item"
-                    @click="$emit('modify-status', status.value)"
+                    @click="$emit('edit-status', status.value)"
                   >
                     {{ status.name }}
                   </span>
@@ -117,7 +117,7 @@
               </div>
             </div>
             <div
-              v-if="checkedFeatures.length > 0 && massMode === 'delete' && isOnline"
+              v-if="checkedFeatures.length > 0 && massMode === 'delete-features' && isOnline"
               class="ui button compact button-hover-red tiny-margin-left"
               data-tooltip="Supprimer tous les signalements sélectionnés"
               data-position="bottom right"
@@ -235,7 +235,12 @@ export default {
           ...initialPagination
         };
       }
-    }
+    },
+    editAttributesFeatureType: {
+      type: String,
+      default: null,
+    },
+
   },
 
   data() {
@@ -334,11 +339,37 @@ export default {
       this.showModifyStatus = false;
     },
 
+    editFeatures() {
+      switch (this.massMode) {
+      case 'edit-status':
+        this.toggleModifyStatus();
+        break;
+      case 'edit-attributes':
+        this.displayAttributesForm();
+        break;
+      }
+    },
+
     toggleModifyStatus() {
       this.showModifyStatus = !this.showModifyStatus;
       this.showAddFeature = false;
     },
 
+    displayAttributesForm() {
+      if (this.checkedFeatures.length > 1) {
+        this.$router.push({
+          name: 'editer-attribut-signalement',
+          params: {
+            slug_type_signal: this.editAttributesFeatureType,
+          },
+        });
+      } else {
+        this.$store.commit('DISPLAY_MESSAGE', {
+          comment: 'Veuillez sélectionner au moins 2 signalements pour l\'édition multiple d\'attributs'
+        });
+      }
+    },
+
     clickOutsideDropdown(e) {
       if (!e.target.closest('#button-dropdown')) {
         this.showModifyStatus = false;
diff --git a/src/router/index.js b/src/router/index.js
index 8fe8e9d76322ca63af83c24d4c95a744059aec64..56953714fbab4848193e56d9fab1c6dcd82e8f04 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -130,6 +130,11 @@ const routes = [
     name: 'editer-signalement',
     component: () => import('../views/Feature/FeatureEdit.vue')
   },
+  {
+    path: `/${projectBase}/:slug/type-signalement/:slug_type_signal/editer-signalements-attributs/`,
+    name: 'editer-attribut-signalement',
+    component: () => import('../views/Feature/FeatureEdit.vue')
+  },
 
   {
     path: '/projet/:slug/catalog/:feature_type_slug',
diff --git a/src/store/index.js b/src/store/index.js
index f5ed15a226fbf68d621784197be62e48aa7c1968..2be432d43f1c6c21dc1de190ecb8cc843cac176b 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -34,6 +34,7 @@ export default new Vuex.Store({
       message: 'En cours de chargement'
     },
     logged: false,
+    messageCount: 0,
     messages: [],
     reloadIntervalId: null,
     staticPages: null,
@@ -75,16 +76,18 @@ export default new Vuex.Store({
       state.levelsPermissions = levelsPermissions;
     },
     DISPLAY_MESSAGE(state, message) {
-      state.messages = [message, ...state.messages];
+      message['counter'] = state.messageCount;
+      state.messageCount += 1;
+      state.messages = [message, ...state.messages]; // add new message at the beginning of the list
       if (document.getElementById('scroll-top-anchor')) {
         document.getElementById('scroll-top-anchor').scrollIntoView({ block: 'start', inline: 'nearest' });
       }
       setTimeout(() => {
-        state.messages = [];
+        state.messages = state.messages.slice(0, -1); // remove one message from the end of the list
       }, 3000);
     },
-    DISCARD_MESSAGE(state, message) {
-      state.messages = state.messages.filter((el) => el.comment !== message.comment);
+    DISCARD_MESSAGE(state, messageCount) {
+      state.messages = state.messages.filter((mess) => mess.counter !== messageCount);
     },
     CLEAR_MESSAGES(state) {
       state.messages = [];
diff --git a/src/store/modules/feature.store.js b/src/store/modules/feature.store.js
index 2466c7c6ad6e4b2c81188aadf9fe694b73d96e6f..7f82a9c848fcf6fa8e385472d0cdc7c5b40b073f 100644
--- a/src/store/modules/feature.store.js
+++ b/src/store/modules/feature.store.js
@@ -15,7 +15,7 @@ const feature = {
     form: null,
     linkedFormset: [], //* used to edit in feature_edit
     linked_features: [], //* used to display in feature_detail
-    massMode: 'modify',
+    massMode: 'edit-status',
   },
   mutations: {
     SET_FEATURES(state, features) {
@@ -174,7 +174,6 @@ const feature = {
 
       const cancelToken = axios.CancelToken.source();
       commit('SET_CANCELLABLE_SEARCH_REQUEST', cancelToken, { root: true });
-      //commit('SET_CURRENT_FEATURE', null); //? Est-ce que c'est nécessaire ? -> fait sauter l'affichage au clic sur un signalement lié (feature_detail)
       const url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature/?id=${feature_id}`;
       return axios
         .get(url, { cancelToken: cancelToken.token })
@@ -186,18 +185,20 @@ const feature = {
           return response;
         })
         .catch((error) => {
+          console.error('Error while getting feature for id = ', feature_id, error);
           throw error;
         });
     },
 
     SEND_FEATURE({ state, rootState, commit, dispatch }, routeName) {
-      function redirect(featureId) {
+      function redirect(featureId, featureName, response) {
+        if (routeName === 'editer-attribut-signalement') return response; // exit function to avoid conflict with next feature call to GET_PROJECT_FEATURE when modifying more than 2 features
         commit(
           'DISPLAY_MESSAGE',
           {
             comment: routeName === 'ajouter-signalement' ?
               'Le signalement a été crée' :
-              'Le signalement a été mis à jour',
+              `Le signalement ${featureName} a été mis à jour`,
             level: 'positive'
           },
           { root: true },
@@ -209,7 +210,7 @@ const feature = {
             feature_id: featureId
           })
           .then(() => {
-            if (routeName.includes('details-signalement')) return;
+            if (routeName === 'details-signalement') return response;
             router.push({
               name: 'details-signalement',
               params: {
@@ -218,24 +219,25 @@ const feature = {
               },
             });
           });
+        return response;
       }
 
-      async function handleOtherForms(featureId) {
+      async function handleOtherForms(featureId, featureName, response) {
         await dispatch('SEND_ATTACHMENTS', featureId);
         await dispatch('PUT_LINKED_FEATURES', featureId);
-        redirect(featureId);
+        return redirect(featureId, featureName, response);
       }
 
       function createGeojson() { //* prepare feature data to send
         const extraFormObject = {}; //* prepare an object to be flatten in properties of geojson
         for (const field of state.extra_forms) {
-          extraFormObject[field.name] = field.value;
+          if (field.value !== null) extraFormObject[field.name] = field.value;
         }
         return {
           id: state.form.feature_id || state.currentFeature.feature_id,
           type: 'Feature',
           geometry: state.form.geometry || state.form.geom ||
-                      state.currentFeature.geometry || state.currentFeature.geom,
+          state.currentFeature.geometry || state.currentFeature.geom,
           properties: {
             title: state.form.title,
             description: state.form.description.value,
@@ -264,12 +266,14 @@ const feature = {
         data: geojson
       }).then((response) => {
         if ((response.status === 200 || response.status === 201) && response.data) {
+          const featureId = response.data.id;
+          const featureName = response.data.properties.title;
           if (state.attachmentFormset.length > 0 ||
             state.linkedFormset.length > 0 ||
             state.attachmentsToDelete.length > 0) {
-            handleOtherForms(response.data.id);
+            return handleOtherForms(featureId, featureName, response);
           } else {
-            redirect(response.data.id);
+            return redirect(featureId, featureName, response);
           }
         }
       })
@@ -296,7 +300,7 @@ const feature = {
             });
           }
           else {
-            console.error(error);
+            console.error('Error while sending feature', error);
             throw error;
           }
           throw error;
diff --git a/src/views/Feature/FeatureEdit.vue b/src/views/Feature/FeatureEdit.vue
index 35eaa28cc17c4b6e0c15fdfb9ed527e20c95f8b0..824c440563985231552addb3ec5c9f1f585280f8 100644
--- a/src/views/Feature/FeatureEdit.vue
+++ b/src/views/Feature/FeatureEdit.vue
@@ -1,12 +1,15 @@
 <template>
   <div id="feature-edit">
-    <h1 v-if="feature && currentRouteName === 'editer-signalement'">
-      Mise à jour du signalement "{{ feature.title || feature.feature_id }}"
-    </h1>
-    <h1
-      v-else-if="feature_type && currentRouteName === 'ajouter-signalement'"
-    >
-      Création d'un signalement <small>[{{ feature_type.title }}]</small>
+    <h1>
+      <span v-if="feature_type && currentRouteName === 'ajouter-signalement'">      
+        Création d'un signalement <small>[{{ feature_type.title }}]</small>
+      </span>
+      <span v-else-if="feature && currentRouteName === 'editer-signalement'">
+        Mise à jour du signalement "{{ feature.title || feature.feature_id }}"
+      </span>
+      <span v-else-if="feature_type && currentRouteName === 'editer-attribut-signalement'">
+        Mise à jour des attributs de {{ checkedFeatures.length }} signalements
+      </span>
     </h1>
 
     <form
@@ -17,7 +20,10 @@
       class="ui form"
     >
       <!-- Feature Fields -->
-      <div class="two fields">
+      <div
+        v-if="currentRouteName !== 'editer-attribut-signalement'"
+        class="two fields"
+      >
         <div :class="field_title">
           <label :for="form.title.id_for_label">{{ form.title.label }}</label>
           <input
@@ -64,7 +70,10 @@
         </div>
       </div>
 
-      <div class="field">
+      <div
+        v-if="currentRouteName !== 'editer-attribut-signalement'"
+        class="field"
+      >
         <label :for="form.description.id_for_label">{{
           form.description.label
         }}</label>
@@ -77,7 +86,10 @@
       </div>
 
       <!-- Geom Field -->
-      <div class="field">
+      <div
+        v-if="currentRouteName !== 'editer-attribut-signalement'"
+        class="field"
+      >
         <label :for="form.geom.id_for_label">{{ form.geom.label }}</label>
         <!-- Import GeoImage -->
         <div
@@ -274,7 +286,7 @@
       </div>
 
       <!-- Pièces jointes -->
-      <div v-if="isOnline">
+      <div v-if="isOnline && currentRouteName !== 'editer-attribut-signalement'">
         <div class="ui horizontal divider">
           PIÈCES JOINTES
         </div>
@@ -304,7 +316,7 @@
       </div>
 
       <!-- Signalements liés -->
-      <div v-if="isOnline">
+      <div v-if="isOnline && currentRouteName !== 'editer-attribut-signalement'">
         <div class="ui horizontal divider">
           SIGNALEMENTS LIÉS
         </div>
@@ -333,7 +345,7 @@
       <button
         type="button"
         :class="['ui teal icon button', { loading: sendingFeature }]"
-        @click="postForm"
+        @click="onSave"
       >
         <i
           class="white save icon"
@@ -439,9 +451,11 @@ export default {
     ]),
     ...mapState('feature', [
       'attachmentFormset',
-      'linkedFormset',
-      'features',
+      'checkedFeatures',
+      'currentFeature',
       'extra_forms',
+      'features',
+      'linkedFormset',
     ]),
     ...mapState('feature-type', [
       'feature_types'
@@ -520,7 +534,7 @@ export default {
       this.$route.params.slug_type_signal
     );
     //* empty previous feature data, not emptying by itself since it doesn't update by itself anymore
-    if (this.currentRouteName === 'ajouter-signalement') {
+    if (this.currentRouteName === 'ajouter-signalement' || this.currentRouteName === 'editer-attribut-signalement') {
       this.$store.commit('feature/SET_CURRENT_FEATURE', []);
     }
 
@@ -531,10 +545,13 @@ export default {
   },
 
   mounted() {
-    const promises = [
-      this.$store.dispatch('projects/GET_PROJECT', this.$route.params.slug),
-      this.$store.dispatch('projects/GET_PROJECT_INFO', this.$route.params.slug),
-    ];
+    const promises = [];
+    if (!this.project) {
+      promises.push(
+        this.$store.dispatch('projects/GET_PROJECT', this.$route.params.slug),
+        this.$store.dispatch('projects/GET_PROJECT_INFO', this.$route.params.slug),
+      );
+    }
     if (this.$route.params.slug_signal) {
       promises.push(
         this.$store.dispatch('feature/GET_PROJECT_FEATURE', {
@@ -545,9 +562,11 @@ export default {
     }
 
     Promise.all(promises).then(() => {
-      this.initForm();
-      this.initMap();
-      this.onFeatureTypeLoaded();
+      if (this.currentRouteName !== 'editer-attribut-signalement') {
+        this.initForm();
+        this.initMap();
+        this.onFeatureTypeLoaded(); // init map tools
+      }
       this.$store.dispatch('feature/INIT_EXTRA_FORMS');
     });
   },
@@ -562,7 +581,7 @@ export default {
 
   methods: {
     initForm() {
-      if (this.currentRouteName === 'editer-signalement') {
+      if (this.currentRouteName.includes('editer')) {
         for (const key in this.feature) {
           if (key && this.form[key]) {
             if (key === 'status') {
@@ -770,22 +789,27 @@ export default {
       return isValid;
     },
 
-    postForm() {
-      let is_valid = true;
-      if (!this.feature_type.title_optional) {
-        is_valid =
-          this.checkFormTitle() &&
-          this.checkFormGeom() &&
-          this.checkAddedForm();
+    onSave() {
+      if (this.currentRouteName === 'editer-attribut-signalement') {
+        this.postMultipleFeatures();
       } else {
-        is_valid = this.checkFormGeom() && this.checkAddedForm();
+        this.postForm();
       }
+    },
+
+    async postForm() {
+      let is_valid = true;
+      let response;
+      is_valid =
+        this.checkFormGeom() &&
+        this.checkAddedForm();
+      if (!this.feature_type.title_optional) is_valid = this.checkFormTitle() && is_valid;
 
       if (is_valid) {
         //* in a moderate project, at edition of a published feature by someone else than admin or moderator, switch published status to draft.
         if (
           this.project.moderation &&
-          this.currentRouteName === 'editer-signalement' &&
+          this.currentRouteName.includes('editer') &&
           this.form.status.value.value === 'published' &&
           !this.permissions.is_project_administrator &&
           !this.permissions.is_project_moderator
@@ -794,9 +818,58 @@ export default {
           this.updateStore();
         }
         this.sendingFeature = true;
-        this.$store.dispatch('feature/SEND_FEATURE', this.currentRouteName)
-          .then(() => this.sendingFeature = false);
+        response = await this.$store.dispatch('feature/SEND_FEATURE', this.currentRouteName);
+        this.sendingFeature = false;
+        return response;
+      }
+    },
+
+    async postMultipleFeatures() {
+      this.$store.commit('DISPLAY_LOADER', 'Envoi des signalements en cours...');
+      const extraForms = [...this.extra_forms];// store extra forms for multiple features to not be overide by current feature
+      let results = [];
+      for (const featureId of this.checkedFeatures) {
+        const response = await this.$store.dispatch('feature/GET_PROJECT_FEATURE', {
+          project_slug: this.$route.params.slug,
+          feature_id: featureId,
+        });
+        if (response.status === 200) {
+          this.initForm(); // fill title, status, description needed to send request
+          for (let xtraForm of extraForms) { // fill extra forms with features values, only if the value of the extra form for multiple features is null
+            if (xtraForm.value === null) { // if no value to overide in feature, keep the feature value
+              xtraForm['value'] = this.feature.feature_data.find((feat) => feat.label === xtraForm.label).value;
+              await this.$store.commit('feature/UPDATE_EXTRA_FORM', xtraForm);
+            }
+          }
+          const result = await this.postForm();
+          results.push(result);
+        }
       }
+      this.$store.commit('DISCARD_LOADER');
+      const errors = results.filter((res) => res === undefined || res.status !== 200);
+      if (errors.length > 0) {
+        this.$store.commit(
+          'DISPLAY_MESSAGE',
+          {
+            comment: 'Des signalements n\'ont pas pu être mis à jour',
+            level: 'negative'
+          },
+        );
+      } else {
+        this.$store.commit(
+          'DISPLAY_MESSAGE',
+          {
+            comment: 'Les signalements ont été mis à jour',
+            level: 'positive'
+          },
+        );
+      }
+      this.$router.push({
+        name: 'liste-signalements',
+        params: {
+          slug: this.$route.params.slug,
+        },
+      });
     },
 
     //* ************* MAP *************** *//
@@ -847,9 +920,9 @@ export default {
       }
     },
 
-    updateGeomField(newGeom) {
+    async updateGeomField(newGeom) {
       this.form.geom.value = newGeom;
-      this.updateStore();
+      await this.updateStore();
     },
 
     initMap() {
diff --git a/src/views/Project/FeaturesListAndMap.vue b/src/views/Project/FeaturesListAndMap.vue
index ebebfcb555d6d666a481aa73b4746ac7cf3ec9f1..fd67094f18b6eb966acf80b64f9fd4dc60007f3b 100644
--- a/src/views/Project/FeaturesListAndMap.vue
+++ b/src/views/Project/FeaturesListAndMap.vue
@@ -5,11 +5,12 @@
         :show-map="showMap"
         :features-count="featuresCount"
         :pagination="pagination"
+        :edit-attributes-feature-type="editAttributesFeatureType"
         @set-filter="setFilters"
         @reset-pagination="resetPagination"
         @fetch-features="fetchPagedFeatures"
         @show-map="setShowMap"
-        @modify-status="modifyStatus"
+        @edit-status="modifyStatus"
         @toggle-delete-modal="toggleDeleteModal"
       />
 
@@ -48,6 +49,7 @@
         :features-count="featuresCount"
         :pagination="pagination"
         :sort="sort"
+        :edit-attributes-feature-type.sync="editAttributesFeatureType"
         :queryparams="queryparams"
         @update:page="handlePageChange"
         @update:sort="handleSortChange"
@@ -125,6 +127,7 @@ export default {
 
   data() {
     return {
+      editAttributesFeatureType: null,
       currentLayer: null,
       featuresCount: 0,
       form: {
@@ -192,6 +195,7 @@ export default {
   },
 
   mounted() {
+    this.UPDATE_CHECKED_FEATURES([]); // empty for when turning back from edit attributes page
     if (!this.project) {
       // Chargements des features et infos projet en cas d'arrivée directe sur la page ou de refresh
       Promise.all([
@@ -224,7 +228,7 @@ export default {
     setShowMap(newValue) {
       this.showMap = newValue;
       //* expanded sidebar is visible under the list, even when the map is closed (position:absolute), solved by closing it whin switching to list
-      if (newValue === false) this.$refs.sidebar.toggleSidebar(false);
+      if (newValue === false && this.$refs.sidebar) this.$refs.sidebar.toggleSidebar(false);
     },
     resetPagination() {
       this.pagination = { ...initialPagination };
@@ -299,6 +303,10 @@ export default {
       this.toggleDeleteModal();
     },
 
+    modifyFeaturesAttributes() {
+      console.log('modifyFeaturesAttributes');
+    },
+
     onFilterChange() {
       if (mapService.getMap() && mapService.mvtLayer) {
         mapService.mvtLayer.changed();