diff --git a/app/models/communication/block/template/contact.rb b/app/models/communication/block/template/contact.rb
index d256e0a056d67c09a41cfc879151572a824a53ca..7897de683e5ed1a617676f6b76f76eb8a6da6bd4 100644
--- a/app/models/communication/block/template/contact.rb
+++ b/app/models/communication/block/template/contact.rb
@@ -24,15 +24,7 @@ class Communication::Block::Template::Contact < Communication::Block::Template::
 
   has_elements
 
-  def has_emails?
-    emails.any?(&:present?)
-  end
-
-  def has_phone_numbers?
-    phone_numbers.any?(&:present?)
-  end
-
-  def has_socials?
+  def socials
     [
       social_mastodon,
       social_x,
@@ -44,7 +36,25 @@ class Communication::Block::Template::Contact < Communication::Block::Template::
       social_facebook,
       social_tiktok,
       social_github
-    ].any?(&:present?)
+    ].compact_blank
+  end
+
+  # We fake children presence.
+  # This is used by the ranks, so we have ranks.children
+  # https://github.com/osunyorg/admin/pull/2505
+  def children
+    [true]
   end
 
+  def has_emails?
+    emails.any?(&:present?)
+  end
+
+  def has_phone_numbers?
+    phone_numbers.any?(&:present?)
+  end
+
+  def has_socials?
+    socials.any?(&:present?)
+  end
 end
diff --git a/app/models/communication/block/with_heading_ranks.rb b/app/models/communication/block/with_heading_ranks.rb
index 352011031cfcf83eef6c6a5a1acbb733f80f2c00..896865fc3b46f7e5122d3f3a6269c753e41d2c34 100644
--- a/app/models/communication/block/with_heading_ranks.rb
+++ b/app/models/communication/block/with_heading_ranks.rb
@@ -1,25 +1,61 @@
 module Communication::Block::WithHeadingRanks
   extend ActiveSupport::Concern
 
-  DEFAULT_HEADING_LEVEL = 2 # h1 is the page title
+  # h1 is the page title
+  DEFAULT_HEADING_LEVEL = 2
 
-  def heading_rank_self
-    title.present? ? heading_rank_base : DEFAULT_HEADING_LEVEL
+  # Title is the block intrinsic title
+  # Not to be confused with `block_title``
+  def heading_self?
+    title.present?
   end
 
-  def heading_rank_children
-    return false unless heading_children?
-    heading_rank_self ? heading_rank_self + 1 : heading_rank_base
+  # Self rank is used for the title (h2 if root, h3 if below a title)
+  def heading_rank_self
+    # No title, no rank self
+    return false unless heading_self?
+    # Otherwise, rank base
+    # Example:
+    # - title h2 (root)
+    # - title h3 (below a title block)
+    heading_rank_base
   end
 
   def heading_children?
     template.children && template.children.any?
   end
 
+  # Rank children is used for the block's children heading
+  def heading_rank_children
+    # If a block has no children, then no rank children
+    return false unless heading_children?
+    # If there's no heading rank self, we take the base rank
+    # Example:
+    # - No title, children h2 (root)
+    # - No title, children h3 (below a title block)
+    return heading_rank_base if heading_rank_self == false
+    # Otherwise, it's the rank self + 1
+    # Example: 
+    # - title h2, children h3 (root)
+    # - title h3, children h4 (below a title block)
+    heading_rank_self + 1
+  end
+
   protected
 
+  # If a block is root, it will have level 2
+  # If a block is below a title block, it will have level 3
+  # Not real yet: if a block is below a subtitle, it will have level 4
+  # There are no subtitles at the moment, so it's all between 2 and 3
   def heading_rank_base
-    block_title.present? ? block_title.heading_rank_self + 1 : DEFAULT_HEADING_LEVEL
+    if block_title.present?
+      # We delegate the rank computing to the block title, and we add 1
+      block_title.heading_rank_self + 1
+    else
+      # If the about has a block base (Education::Program::Localization), we use it
+      # Otherwise, default level (2)
+      about.try(:blocks_heading_rank_base) || DEFAULT_HEADING_LEVEL
+    end
   end
 
   # A block can belong to a title, meaning it is below the title
diff --git a/app/models/education/program/localization.rb b/app/models/education/program/localization.rb
index 6d8e4e4e8728a72e4b5a2cf5930bd06798e47ead..ed1a8032038dac18c3e82d40a1f45e42c12ecfa7 100644
--- a/app/models/education/program/localization.rb
+++ b/app/models/education/program/localization.rb
@@ -115,6 +115,10 @@ class Education::Program::Localization < ApplicationRecord
     [parent]
   end
 
+  def blocks_heading_rank_base
+    3
+  end
+
   def best_featured_image_source(fallback: true)
     return self if featured_image.attached?
     best_source = parent&.best_featured_image_source(fallback: false)
diff --git a/app/views/admin/communication/blocks/_static.html.erb b/app/views/admin/communication/blocks/_static.html.erb
index a16e67127a2a9a60ccf7b8e09d18ea9b92618e51..d9c9a2a70824fc51ba05e642e0aa7c6b564a6711 100644
--- a/app/views/admin/communication/blocks/_static.html.erb
+++ b/app/views/admin/communication/blocks/_static.html.erb
@@ -9,7 +9,9 @@ should_render_data = block.data && block.data.present?
     slug: >-
       <%= block.slug %>
     ranks:
+<% if block.heading_self? %>
       self: <%= block.heading_rank_self %>
+<% end %>
 <% if block.heading_children? %>
       children: <%= block.heading_rank_children %>
 <% end %>