diff --git a/src/api/dataset.ts b/src/api/dataset.ts
index ca791fc3ce8aeb37fe33b1f3142d86c7fa94b2ce..04744c66802f6dbaf502328f0c531a67915d9af3 100644
--- a/src/api/dataset.ts
+++ b/src/api/dataset.ts
@@ -1,6 +1,6 @@
 import axios from 'axios'
 import { PageNumberPaginationParameters, unique } from '.'
-import { Dataset, PageNumberPagination, UUID } from '@/types'
+import { Dataset, ElementDataset, PageNumberPagination, UUID } from '@/types'
 
 interface DatasetCreate extends Omit<Dataset, 'id' | 'state'> {
   corpusId: UUID
@@ -21,15 +21,28 @@ export const listCorpusDataset = unique(
 )
 
 export const createDataset = unique(
-  async ({corpusId, ...data}: DatasetCreate): Promise<Dataset> =>
+  async ({ corpusId, ...data }: DatasetCreate): Promise<Dataset> =>
     (await axios.post(`/corpus/${corpusId}/datasets/`, data)).data
 )
 
 export const updateDataset = unique(
-  async ({id, ...data}: DatasetEdit): Promise<Dataset> =>
+  async ({ id, ...data }: DatasetEdit): Promise<Dataset> =>
     (await axios.patch(`/datasets/${id}/`, data)).data
 )
 
 export const deleteDataset = unique(
   async (id: UUID) => await axios.delete(`/datasets/${id}/`)
-)
\ No newline at end of file
+)
+
+export interface ElementDatasetParams extends PageNumberPaginationParameters {
+  /**
+   * ID of the element to list dataset from.
+   */
+  eltId: UUID
+}
+
+// List datasets containing a specific element
+export const listElementDatasets = unique(
+  async ({ eltId, ...params }: ElementDatasetParams): Promise<PageNumberPagination<ElementDataset>> =>
+    (await axios.get(`/element/${eltId}/datasets/`, { params })).data
+)
diff --git a/src/components/Element/DetailsPanel.vue b/src/components/Element/DetailsPanel.vue
index 20d9a7961c3fe58f6b3a39c6b135326b462bcade..5896916d1335f6134e5422bff9d7b47a34a46186 100644
--- a/src/components/Element/DetailsPanel.vue
+++ b/src/components/Element/DetailsPanel.vue
@@ -63,6 +63,26 @@
       </DropdownContent>
       <hr />
 
+      <DropdownContent id="datasets" title="Datasets">
+        <div class="loader m-auto is-size-3" v-if="datasets === null">
+        </div>
+        <div class="message-body has-text-grey" v-else-if="datasets.length === 0">
+          No datasets
+        </div>
+        <table v-else class="table is-fullwidth is-hoverable">
+          <thead>
+            <th>Name</th>
+            <th>Set</th>
+          </thead>
+          <tbody>
+            <tr v-for="dataset in datasets" :key="dataset.id + dataset.set">
+              <td>{{ dataset.dataset_name }}</td>
+              <td>{{ dataset.set }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </DropdownContent>
+
       <EntityLinks
         :element-id="element.id"
         v-if="elementType.folder === false"
@@ -71,21 +91,30 @@
   </div>
 </template>
 
-<script>
-import { mapState, mapGetters } from 'vuex'
+<script lang="ts">
+import {
+  mapState as mapVuexState,
+  mapGetters as mapVuexGetters,
+  mapActions as mapVuexActions
+} from 'vuex'
 import { corporaMixin } from '@/mixins'
+import { defineComponent, PropType } from 'vue'
+import { UUID_REGEX } from '@/config'
+import { UUID, Element, ElementDataset } from '@/types'
+import { mapState, mapActions } from 'pinia'
+import { useElementsStore } from '@/stores'
 
 import DropdownContent from '@/components/DropdownContent.vue'
 import MLClassSelect from '@/components/MLClassSelect.vue'
 import EntityLinks from '@/components/Entity/Links.vue'
-import Classifications from './Classifications'
-import ElementMetadata from './Metadata'
-import OrientationPanel from './OrientationPanel'
-import Transcriptions from './Transcription'
-import TranscriptionsModal from './Transcription/Modal'
-import TranscriptionCreationForm from './Transcription/CreationForm'
+import Classifications from '@/components/Element/Classifications'
+import ElementMetadata from '@/components/Element/Metadata'
+import OrientationPanel from '@/components/Element/OrientationPanel.vue'
+import Transcriptions from '@/components/Element/Transcription'
+import TranscriptionsModal from '@/components/Element/Transcription/Modal.vue'
+import TranscriptionCreationForm from '@/components/Element/Transcription/CreationForm.vue'
 
-export default {
+export default defineComponent({
   mixins: [
     corporaMixin
   ],
@@ -101,8 +130,12 @@ export default {
     TranscriptionsModal
   },
   props: {
+    /**
+     * Id of the element
+     */
     elementId: {
-      type: String,
+      type: String as PropType<UUID>,
+      validator: value => typeof value === 'string' && UUID_REGEX.test(value),
       required: true
     }
   },
@@ -113,18 +146,19 @@ export default {
     transcriptionModal: false
   }),
   computed: {
-    ...mapState('elements', ['elements', 'transcriptions']),
-    ...mapState('process', ['workerVersions', 'workers']),
-    ...mapState('classification', ['hasMlClasses']),
-    ...mapGetters('elements', {
+    ...mapVuexState('elements', ['elements', 'transcriptions']),
+    ...mapVuexState('process', ['workerVersions', 'workers']),
+    ...mapVuexState('classification', ['hasMlClasses']),
+    ...mapState(useElementsStore, ['elementDatasets']),
+    ...mapVuexGetters('elements', {
       // canWrite and canAdmin are already defined in corporaMixin
       canWriteElement: 'canWrite',
       canAdminElement: 'canAdmin'
     }),
-    element () {
-      return this.elements[this.elementId]
+    element (): Element | null {
+      return this.elements?.[this.elementId] ?? null
     },
-    corpusId () {
+    corpusId (): UUID | null {
       return this.element?.corpus?.id ?? null
     },
     elementType () {
@@ -144,20 +178,27 @@ export default {
       return (this.element && this.element.classifications) || []
     },
     metadata () {
-      return (this.element && this.element.metadata) || []
+      // @ts-expect-error Some Element attributes like metadata are set on the fly on the former store
+      return (this.element && this.element?.metadata) || []
+    },
+    datasets (): ElementDataset[] | null {
+      return this.elementDatasets?.[this.elementId] ?? null
     }
   },
   methods: {
+    ...mapVuexActions('classification', { classificationCreate: 'create' }),
+    ...mapVuexActions('elements', { retrieveElement: 'get' }),
+    ...mapActions(useElementsStore, ['listElementDatasets']),
     async createClassification () {
       if (!this.canCreateClassification) return
       this.isSavingNewClassification = true
       try {
-        await this.$store.dispatch('classification/create', {
+        await this.classificationCreate({
           elementId: this.elementId,
           mlClass: this.selectedNewClassification
         })
       } finally {
-        this.$refs.newClassificationSelect?.clear()
+        this.selectedNewClassification = ''
         this.isSavingNewClassification = false
       }
     }
@@ -165,7 +206,7 @@ export default {
   watch: {
     elementId: {
       immediate: true,
-      handler (id) {
+      handler (id: UUID) {
         if (!id) return
         /*
          * Do not retrieve the element again if it already exists in the store,
@@ -174,14 +215,20 @@ export default {
          * This ensures there are no strange behaviors where some actions are only sometimes disabled when they shouldn't,
          * or some element attributes are not displayed at all.
          */
-        if (!this.element || this.element.id !== id || !this.element.rights || !this.element.classifications) this.$store.dispatch('elements/get', { id })
+        if (!this.element || this.element.id !== id || !this.element.rights || !this.element.classifications) this.retrieveElement({ id })
+      }
+    },
+    datasets: {
+      immediate: true,
+      async handler (value) {
+        if (value === null) this.listElementDatasets({ eltId: this.elementId })
       }
     },
     selectedNewClassification () {
       this.createClassification()
     }
   }
-}
+})
 </script>
 
 <style scoped>
diff --git a/src/store/index.js b/src/store/index.js
index 311957e27c3347c5d19fea2ba0c9d753bc83ec11..bb79687556a5d32246180ddb20042974b44a7532 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,6 +1,7 @@
 import { createStore } from 'vuex'
 import {
   useDisplayStore,
+  useElementsStore,
   useFolderPickerStore,
   useImageStore,
   useIngestStore,
@@ -38,6 +39,7 @@ const moduleNames = [
  */
 export const piniaStores = [
   useDisplayStore,
+  useElementsStore,
   useFolderPickerStore,
   useImageStore,
   useIngestStore,
diff --git a/src/stores/elements.ts b/src/stores/elements.ts
new file mode 100644
index 0000000000000000000000000000000000000000..429bca6547df6f4c0df4abd894b35fc2f533d345
--- /dev/null
+++ b/src/stores/elements.ts
@@ -0,0 +1,45 @@
+import { defineStore } from 'pinia'
+import { listElementDatasets as listElementDatasetsAPI, ElementDatasetParams, unique } from '@/api'
+import { errorParser } from '@/helpers'
+import { ElementDataset, UUID, PageNumberPagination } from '@/types'
+import { useNotificationStore } from '@/stores'
+import { isAxiosError } from 'axios'
+import axios from 'axios'
+
+interface State {
+  /**
+   * List of dataset that contains a specific element
+   */
+  elementDatasets: { [elementId: UUID]: ElementDataset[] }
+}
+
+export const useElementsStore = defineStore('elements', {
+  state: (): State => ({
+    elementDatasets: {}
+  }),
+  actions: {
+    async listElementDatasets (data: ElementDatasetParams, url: string | null = null) {
+      // Avoid listing datasets twice for an element
+      if (url === null && (this.elementDatasets?.[data.eltId] || null) !== null) return
+      // List datasets containing a specific element through all pages
+      //let results, pagination
+      try {
+        const { results, ...pagination } = (
+          url === null ?
+          await listElementDatasetsAPI(data)
+          : (await axios.get(url)).data as PageNumberPagination<ElementDataset>
+        )
+        // Progressively add results to the store
+        this.elementDatasets[data.eltId] = [
+          ...this.elementDatasets?.[data.eltId] || [],
+          ...results
+        ]
+        // Follow pagination without awaiting, until we fetched all the data
+        if (pagination.next !== null) this.listElementDatasets(data, pagination.next)
+      } catch (err) {
+        useNotificationStore().notify({ type: 'error', text: errorParser(err) })
+        throw err
+      }
+    }
+  }
+})
diff --git a/src/stores/index.ts b/src/stores/index.ts
index 4bdcee58b34872d2579b305b58657e35bcd6aa4f..79ea0d28cb0fdf18ce1464b67d61a89d3d9b0431 100644
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -1,4 +1,5 @@
 export { useDisplayStore } from './display'
+export { useElementsStore } from './elements'
 export { useFolderPickerStore } from './folderpicker'
 export { useImageStore } from './image'
 export { useIngestStore } from './ingest'
diff --git a/src/types/index.ts b/src/types/index.ts
index 3b8f833cc9509c1c3a7ad7c4eb2836e8e5aec8fe..0aeb06ef64c0bdf8655dcb537a5c277585180947 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -306,6 +306,12 @@ export interface Dataset {
   sets: string[]
 }
 
+export interface ElementDataset {
+  dataset_id: UUID
+  dataset_name: string
+  set: string
+}
+
 export type JobStatus = 'queued' | 'started' | 'deferred' | 'finished' | 'stopped' | 'scheduled' | 'canceled' | 'failed'
 
 /**
diff --git a/tests/unit/samples.js b/tests/unit/samples.js
index b699aeda7d9d668d14f57440592d568f7e53222c..f647e955f894d3d219de169aaea80c1c9bca6ac3 100644
--- a/tests/unit/samples.js
+++ b/tests/unit/samples.js
@@ -636,3 +636,15 @@ export const s3ProcessSample = {
   mode: 's3',
   corpus: 'some corpus'
 }
+
+export const elementDatasetsSample = makeSampleResults([
+  {
+    dataset_id: 'dataset_1',
+    dataset_name: 'dataset',
+    set: 'test'
+  }, {
+    dataset_id: 'dataset_1',
+    dataset_name: 'dataset',
+    set: 'train'
+  }
+])
diff --git a/tests/unit/stores/elements.spec.js b/tests/unit/stores/elements.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a89d8ae6ff39e0f5d0d14d23a349a6851b78cf09
--- /dev/null
+++ b/tests/unit/stores/elements.spec.js
@@ -0,0 +1,76 @@
+import { assert } from 'chai'
+import { setActivePinia } from 'pinia'
+import axios from 'axios'
+import { useElementsStore, useNotificationStore } from '@/stores'
+import { assertRejects, FakeAxios, setUpTestPinia, actionsCompletedPlugin } from '../testhelpers.js'
+import { elementDatasetsSample } from '../samples.js'
+
+describe('elements.ts', () => {
+  let pinia, mock, store, notificationStore
+
+  before(() => {
+    [pinia] = setUpTestPinia([actionsCompletedPlugin])
+    setActivePinia(pinia)
+    mock = new FakeAxios(axios)
+    store = useElementsStore()
+    notificationStore = useNotificationStore()
+  })
+
+  beforeEach(() => {
+    mock.reset()
+    store.$reset()
+    notificationStore.$reset()
+  })
+
+  after('Removing Axios mock', () => {
+    mock.restore()
+  })
+
+  describe('actions', () => {
+    describe('listElementDatasets', () => {
+      it('returns early in case datasets have been fetched', async () => {
+        store.elementDatasets = { element_id: [] }
+        await store.listElementDatasets({ eltId: 'element_id' })
+        await store.actionsCompleted()
+      })
+
+      it('automatically loads datasets for an element', async () => {
+        const pagination1 = {
+          count: 2,
+          previous: null,
+          next: '/element/element_id/datasets/?page=2',
+          results: [elementDatasetsSample.results[0]]
+        }
+        const pagination2 = {
+          count: 2,
+          previous: 'http://previous',
+          next: null,
+          results: [elementDatasetsSample.results[1]]
+        }
+        mock.onGet('/element/element_id/datasets/').reply(200, pagination1)
+        mock.onGet('/element/element_id/datasets/?page=2').reply(200, pagination2)
+
+        await store.listElementDatasets({ eltId: 'element_id' })
+        await store.actionsCompleted()
+
+        assert.deepStrictEqual(store.elementDatasets, {
+          element_id: [
+            elementDatasetsSample.results[0],
+            elementDatasetsSample.results[1]
+          ]
+        })
+      })
+
+      it('handles errors', async () => {
+        mock.onGet('/element/element_id/datasets/').reply(403, { detail: ['Forbidden'] })
+
+        await assertRejects(async () =>
+          await store.listElementDatasets({ eltId: 'element_id' })
+        )
+        assert.deepStrictEqual(notificationStore.notifications, [
+          { id: 0, type: 'error', text: 'Forbidden' }
+        ])
+      })
+    })
+  })
+})