diff --git a/app/models/communication/block/template/testimonial.rb b/app/models/communication/block/template/testimonial.rb
index 4da01d1b9a676d131e1ce8c3e6290204d7ab399e..70e9d702694e8fc6a90cfaded437959b3656314d 100644
--- a/app/models/communication/block/template/testimonial.rb
+++ b/app/models/communication/block/template/testimonial.rb
@@ -1,23 +1,5 @@
 class Communication::Block::Template::Testimonial < Communication::Block::Template::Base
-  def build_git_dependencies
-    add_dependency active_storage_blobs
-  end
 
-  def testimonials
-    @testimonials ||= elements.map { |element| testimonial(element) }
-                              .compact
-  end
+  has_elements Communication::Block::Template::Testimonial::Testimonial
 
-  def active_storage_blobs
-    @active_storage_blobs ||= testimonials.map { |testimonial| testimonial.blob }
-                                          .compact
-  end
-
-  protected
-
-  def testimonial(element)
-    blob = find_blob element, 'photo'
-    element['blob'] = blob
-    element.to_dot
-  end
 end
diff --git a/app/models/communication/block/template/testimonial/testimonial.rb b/app/models/communication/block/template/testimonial/testimonial.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2127e86be8907269856c9c21479eda193322be25
--- /dev/null
+++ b/app/models/communication/block/template/testimonial/testimonial.rb
@@ -0,0 +1,8 @@
+class Communication::Block::Template::Testimonial::Testimonial < Communication::Block::Template::Base
+
+  has_text :text
+  has_string :author
+  has_string :job
+  has_image :photo
+
+end
diff --git a/app/models/communication/block/template/timeline.rb b/app/models/communication/block/template/timeline.rb
index 8dcc3d98a7ae2c19f64da6d681ae333d086f0a47..03c0f668ca87bb4ec56b0eef038bc6c6a9c89ccf 100644
--- a/app/models/communication/block/template/timeline.rb
+++ b/app/models/communication/block/template/timeline.rb
@@ -1,17 +1,5 @@
 class Communication::Block::Template::Timeline < Communication::Block::Template::Base
 
-  def description
-    "#{data['description']}"
-  end
+  has_elements Communication::Block::Template::Timeline::Timeline
 
-  def events
-    @events ||= elements.map { |element| event(element) }
-                              .compact
-  end
-
-  protected
-
-  def event(element)
-    element.to_dot
-  end
 end
diff --git a/app/models/communication/block/template/timeline/timeline.rb b/app/models/communication/block/template/timeline/timeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85b28ef7849682ba70c3219aa5ded14434c1cebd
--- /dev/null
+++ b/app/models/communication/block/template/timeline/timeline.rb
@@ -0,0 +1,6 @@
+class Communication::Block::Template::Testimonial::Testimonial < Communication::Block::Template::Base
+
+  has_string :author
+  has_text :text
+
+end
diff --git a/app/views/admin/communication/blocks/templates/testimonials/_edit.html.erb b/app/views/admin/communication/blocks/templates/testimonials/_edit.html.erb
index 7a5666854d549272d51eda15bcd6a1436f650231..a485b64584f04df696e95d8ca7de0522729b72d5 100644
--- a/app/views/admin/communication/blocks/templates/testimonials/_edit.html.erb
+++ b/app/views/admin/communication/blocks/templates/testimonials/_edit.html.erb
@@ -1,10 +1,9 @@
-<a  class="<%= button_classes('mb-4') %>"
-    v-on:click="data.elements.push({text: '', author: '', job: '', photo: {}})">
-    <%= t '.add_testimonial' %>
-</a>
+<% element = @block.template.default_element %>
+
+<%= block_component_add_element t('.add_testimonial') %>
 
 <draggable :list="data.elements" handle=".dragHandle" class="row">
-  <div v-for="(testimonial, index) in data.elements" class="list-group-item">
+  <div v-for="(element, index) in data.elements" class="list-group-item">
     <div class="d-flex">
       <div>
         <a class="btn ps-0 pt-0 dragHandle">
@@ -14,58 +13,14 @@
       <div class="flex-fill">
         <div class="row">
           <div class="col-lg-4">
-            <label    class="form-label"
-                      :for="'testimonial-' + index + '-text'"><%= t '.text_label' %></label>
-            <textarea class="form-control mb-3"
-                      rows="3"
-                      v-model="testimonial.text"
-                      placeholder="<%= t '.text_placeholder' %>"
-                      :id="'testimonial-' + index + '-text'"></textarea>
+            <%= block_component_edit :text, template: element %>
           </div>
           <div class="col-lg-4">
-            <label  class="form-label"
-                    :for="'testimonial-' + index + '-author'">
-              <%= t '.author_label' %>
-            </label>
-            <input  class="form-control mb-3"
-                    type="text"
-                    v-model="testimonial.author"
-                    placeholder="<%= t '.author_placeholder' %>"
-                    :id="'testimonial-' + index + '-author'">
-            <label  class="form-label"
-                    :for="'testimonial-' + index + '-job'">
-              <%= t '.job_label' %>
-            </label>
-            <input  class="form-control mb-3"
-                    type="text"
-                    v-model="testimonial.job"
-                    placeholder="<%= t '.job_placeholder' %>"
-                    :id="'testimonial-' + index + '-job'">
+            <%= block_component_edit :author, template: element %>
+            <%= block_component_edit :job, template: element %>
           </div>
           <div class="col-lg-4">
-            <div v-if="!testimonial.photo.id">
-              <%# TODO : create a uploader vue3 component %>
-              <label  class="form-label"
-                      :for="'testimonial-' + index + '-photo'">
-                <%= t '.photo_label' %>
-              </label>
-              <input  class="form-control mb-3"
-                      type="file"
-                      accept="image/*"
-                      @change="onFileImageChange( $event, testimonial, 'photo' )"
-                      :id="'testimonial-' + index + '-photo'">
-            </div>
-            <div v-if="testimonial.photo.id">
-              <img :src="getImageUrl(testimonial.photo)"
-                    class="img-fluid"
-                    style="max-height: 80px"
-                    />
-              <a  class="btn btn-sm btn-danger ms-2"
-                  v-on:click="testimonial.photo={}">
-                  <i class="fas fa-times"></i>
-                  <%= t '.remove_photo' %>
-              </a>
-            </div>
+            <%= block_component_edit :photo, template: element %>
           </div>
         </div>
       </div>
diff --git a/app/views/admin/communication/blocks/templates/testimonials/_preview.html.erb b/app/views/admin/communication/blocks/templates/testimonials/_preview.html.erb
index e12a4fc2630069d82954b3322538a01cd43c40e8..fc8ca728c4f42b954c2a5977262def70fc05c5bd 100644
--- a/app/views/admin/communication/blocks/templates/testimonials/_preview.html.erb
+++ b/app/views/admin/communication/blocks/templates/testimonials/_preview.html.erb
@@ -1,22 +1,22 @@
-<% @block.template.testimonials.each do |testimonial| %>
+<% @block.template.elements.each do |element| %>
+
   <article class="card">
     <div class="card-body">
       <p class="lead">
-        <%= testimonial.text %>
+        <%= block_component_preview :text, template: element %>
       </p>
       <div class="d-flex align-items-center">
-        <% if testimonial.blob  %>
+        <% if element.photo  %>
           <div style="max-width: 80px;" class="me-3">
-            <%= kamifusen_tag testimonial.blob,
-                              width: 80,
-                              class: 'img-fluid rounded-circle img-circle' %>
+             <%= block_component_preview :photo, template: element %>
           </div>
         <% end %>
         <p class="flex-fill mb-0">
-          <b><%= testimonial.author %></b><br>
-          <%= testimonial.job %>
+          <b><%= block_component_preview :author, template: element %></b><br>
+          <%= block_component_preview :job, template: element %>
         </p>
       </div>
     </div>
   </article>
+
 <% end %>
diff --git a/app/views/admin/communication/blocks/templates/testimonials/_static.html.erb b/app/views/admin/communication/blocks/templates/testimonials/_static.html.erb
index 82dd7b3e10b3e39d6ab1256c85f46a5c26f6e452..21f61f9b7805cf92ba48cbdce68e6b898f69aa62 100644
--- a/app/views/admin/communication/blocks/templates/testimonials/_static.html.erb
+++ b/app/views/admin/communication/blocks/templates/testimonials/_static.html.erb
@@ -1,12 +1,11 @@
       testimonials:
-<% block.template.testimonials.each do |testimonial| %>
-        - text: >-
-            <%= prepare_text_for_static testimonial.text, 6 %>
-          author: >-
-            <%= prepare_text_for_static testimonial.author, 6 %>
-          job: >-
-            <%= prepare_text_for_static testimonial.job, 6 %>
-<% if testimonial.blob %>
-          photo: "<%= testimonial.blob.id %>"
+<% block.template.elements.each do |element| %>
+      Testimonial:
+<%= block_component_static :text, template: element, depth: 5 %>
+<%= block_component_static :author, template: element, depth: 5 %>
+<%= block_component_static :job, template: element, depth: 5 %>
+<% if element.photo %>
+<%= block_component_static :photo, template: element, depth: 5 %>
 <% end %>
+
 <% end %>
diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml
index 785cd45364ba97c09776d7e1c038ae1b8afa6abc..1cd1e102aceb32115c2e055fd216feec83890bc4 100644
--- a/config/locales/communication/en.yml
+++ b/config/locales/communication/en.yml
@@ -307,15 +307,20 @@ en:
             description: One or more testimonies
             edit:
               add_testimonial: Add testimonial
-              text_label: Text
-              text_placeholder: Enter testimonial's text
-              author_label: Author of the text
-              author_placeholder: Enter authors' name
-              job_label: Author's job
-              job_placeholder: Enter authors' job
-              photo_label: Photo
-              remove_photo: Remove photo
               remove_testimonial: Remove testimonial
+              element:
+                text:
+                  label: Text
+                  placeholder: Enter testimonial's text
+                author:
+                  label: Author of the text
+                  placeholder: Enter authors' name
+                job:
+                  label: Author's job
+                  placeholder: Enter authors' job
+                photo:
+                  label: Photo
+                  remove_photo: Remove photo
           timeline:
             description: A list of events with their description, on a timeline.
             edit:
diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml
index 42ff75c22e39afd0ddd5ab1b11ee6c17fc554cb8..816b0b3884b34da255d71331512d10cb7286b67d 100644
--- a/config/locales/communication/fr.yml
+++ b/config/locales/communication/fr.yml
@@ -322,27 +322,36 @@ fr:
           testimonials:
             description: Un ou plusieurs témoignages, avec le texte, l'auteur, sa fonction et sa photo.
             edit:
-              add_testimonial: Ajouter un témoignage
-              text_label: Texte
-              text_placeholder: Entrer le texte du témoignage
-              author_label: Auteur·e du témoignage
-              author_placeholder: Entrer le nom de l'auteur·e
-              job_label: Métier de l'auteur·e
-              job_placeholder: Entrer le métier de l'auteur·e
-              photo_label: Photo
-              remove_photo: Enlever la photo
-              remove_testimonial: Enlever le témoignage
+              element:
+                add_testimonial: Ajouter un témoignage
+                remove_testimonial: Enlever le témoignage
+                text:
+                  label: Texte
+                  placeholder: Entrer le texte du témoignage
+                author:
+                  label: Auteur·e du témoignage
+                  placeholder: Entrer le nom de l'auteur·e
+                job:
+                  label: Métier de l'auteur·e
+                  placeholder: Entrer le métier de l'auteur·e
+                photo:
+                  label: Photo
+                  remove_photo: Enlever la photo
           timeline:
             description: Une liste d'événements avec leur description, présentés en frise.
             edit:
-              description_label: Description
-              description_placeholder: Entrer la description
               add_event: Ajouter un événement
               remove_event: Supprimer l'événement
-              title_label: Titre
-              title_placeholder: Entrer le titre de l'événement
-              text_label: Texte
-              text_placeholder: Entrer le texte de l'événement
+              element:
+                description:
+                  label: Description
+                  placeholder: Entrer la description
+                title:
+                  label: Titre
+                  placeholder: Entrer le titre de l'événement
+                text:
+                  label: Texte
+                  placeholder: Entrer le texte de l'événement
           video:
             description: Une vidéo intégrée dans la page depuis diverses plateformes, avec la transcription et sans lecture automatique.
             edit: