diff --git a/district/models.py b/district/models.py
index 17bbde63e932181651be2a3ac1db4d37227d4f67..5a68c0fb7d1e8bbeb72bad8639f40a47d0215a0c 100644
--- a/district/models.py
+++ b/district/models.py
@@ -48,10 +48,12 @@ from shared.blocks import (
 from shared.const import RICH_TEXT_DEFAULT_FEATURES
 from shared.models import (
     ArticleMixin,
+    ArticlesMixin,
     ExtendedMetadataHomePageMixin,
     ExtendedMetadataPageMixin,
     FooterMixin,
     MenuMixin,
+    SharedTaggedDistrictArticle,
     SubpageMixin,
 )
 from shared.utils import make_promote_panels, strip_all_html_tags, trim_to_length
@@ -403,6 +405,9 @@ class DistrictArticlePage(
     )
     is_black = models.BooleanField("Má tmavé pozadí?", default=False)
     tags = ClusterTaggableManager(through=DistrictArticleTag, blank=True)
+    shared_tags = ClusterTaggableManager(
+        through=SharedTaggedDistrictArticle, blank=True
+    )
     thumb_image = models.ForeignKey(
         "wagtailimages.Image",
         on_delete=models.PROTECT,
@@ -418,6 +423,7 @@ class DistrictArticlePage(
         FieldPanel("author_page"),
         FieldPanel("is_black"),
         FieldPanel("tags"),
+        FieldPanel("shared_tags"),
         FieldPanel("thumb_image"),
     ]
 
@@ -460,7 +466,12 @@ class DistrictArticlePage(
 
 
 class DistrictArticlesPage(
-    RoutablePageMixin, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page
+    RoutablePageMixin,
+    ExtendedMetadataPageMixin,
+    SubpageMixin,
+    MetadataPageMixin,
+    ArticlesMixin,
+    Page,
 ):
     ### FIELDS
 
@@ -471,7 +482,7 @@ class DistrictArticlesPage(
 
     ### PANELS
 
-    content_panels = Page.content_panels + [
+    content_panels = ArticlesMixin.content_panels + [
         FieldPanel("max_items"),
     ]
 
@@ -524,10 +535,7 @@ class DistrictArticlesPage(
     def get_context(self, request):
         context = super().get_context(request)
         context["articles"] = Paginator(
-            self.get_children()
-            .live()
-            .specific()
-            .order_by("-districtarticlepage__date"),
+            self.append_all_shared_articles(self.get_children().live().specific()),
             self.max_items,
         ).get_page(request.GET.get("page"))
         return context
@@ -540,15 +548,19 @@ class DistrictArticlesPage(
             context=self.get_tags_page_context(request=request),
         )
 
+    @route(r"^sdilene/$", name="shared")
+    def shared(self, request):
+        return self.setup_article_page_context(request)
+
     def get_tags_page_context(self, request) -> dict:
         # Potřebujeme IDčka článků pro správnou root_page pro filtrování zobrazených
         # tagů i samotných stránek, protože se filtrují přes specifický field
         # (tags__slug)
         context = super().get_context(request)
 
-        site_article_ids = (
-            self.get_children().live().specific().values_list("id", flat=True)
-        )
+        site_article_ids = self.append_all_shared_articles(
+            self.get_children().live().specific()
+        ).values_list("id", flat=True)
 
         # Naplním "tag" a "article_page_list" parametry
         context.update(**self.get_tag_and_articles(request, site_article_ids))
@@ -564,10 +576,12 @@ class DistrictArticlesPage(
         pro danou stránku (site_article_ids). Lepší by bylo články a tag řešit
         separátně, ale pak by se musel rozpadnout ten try/except na více bloků.
         """
-        article_page_qs = DistrictArticlePage.objects.filter(id__in=site_article_ids)
+        article_page_qs = self.append_all_shared_articles(
+            DistrictArticlePage.objects
+        ).filter(id__in=site_article_ids)
 
         try:
-            tag = DistrictArticleTag.objects.filter(tag__slug=request.GET["tag"])[0].tag
+            tag = Tag.objects.filter(tag__slug=request.GET["tag"])[0].tag
             article_page_qs = article_page_qs.filter(tags__slug=tag.slug)
         except (KeyError, IndexError):
             tag = None
@@ -580,14 +594,13 @@ class DistrictArticlesPage(
             "tag": tag,
         }
 
-    @staticmethod
-    def get_tag_qs(site_article_ids: list) -> models.QuerySet:
+    def get_tag_qs(self, site_article_ids: list) -> models.QuerySet:
         """
         Getuje Tagy pouze pro DistrictArticlePage omezeno IDčky getnutých přes
         root_page. Počítá, kolik článků je s daným tagem.
         """
         return (
-            Tag.objects.filter(districtarticlepage__id__in=site_article_ids)
+            Tag.objects.filter(**self.get_search_tags_params(site_article_ids))
             .order_by("slug")
             .annotate(count=models.Count("slug"))
             .values("name", "slug", "count")
diff --git a/main/models.py b/main/models.py
index 26928fc3503195efdfca26a94dff8ebb759feb34..2afb57ff684863a53f54fe4c22a9a1fe7b1e17dc 100644
--- a/main/models.py
+++ b/main/models.py
@@ -31,12 +31,14 @@ from wagtailmetadata.models import MetadataPageMixin
 
 from calendar_utils.models import CalendarMixin
 from elections2021.constants import REGION_CHOICES  # pozor, import ze sousedního modulu
-from instagram_utils.models import InstagramPost, InstagramMixin
+from instagram_utils.models import InstagramMixin, InstagramPost
 from shared.forms import SubscribeForm
 from shared.models import (  # MenuMixin,
     ArticleMixin,
+    ArticlesMixin,
     ExtendedMetadataHomePageMixin,
     ExtendedMetadataPageMixin,
+    SharedTaggedMainArticle,
     SubpageMixin,
 )
 from shared.utils import make_promote_panels, subscribe_to_newsletter
@@ -59,7 +61,7 @@ class MainHomePage(
     ExtendedMetadataHomePageMixin,
     MetadataPageMixin,
     InstagramMixin,
-    Page
+    Page,
 ):
     # header
 
@@ -357,7 +359,12 @@ class MainHomePage(
 
 
 class MainArticlesPage(
-    RoutablePageMixin, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page
+    RoutablePageMixin,
+    ExtendedMetadataPageMixin,
+    SubpageMixin,
+    MetadataPageMixin,
+    ArticlesMixin,
+    Page,
 ):
     perex = models.TextField()
     last_import_log = models.TextField(
@@ -391,7 +398,7 @@ class MainArticlesPage(
     subpage_types = ["main.MainArticlePage"]
 
     ### PANELS
-    content_panels = Page.content_panels + [FieldPanel("perex")]
+    content_panels = ArticlesMixin.content_panels + [FieldPanel("perex")]
     promote_panels = make_promote_panels()
 
     ### EDIT HANDLERS
@@ -508,7 +515,9 @@ class MainArticlesPage(
 
     def get_all_articles_search_response(self, request):
         article_paginator = Paginator(
-            MainArticlePage.objects.live().order_by("-date").search(request.GET["q"]),
+            self.append_all_shared_articles(MainArticlePage.objects.live()).search(
+                request.GET["q"]
+            ),
             10,
         )
         article_page = article_paginator.get_page(request.GET.get("page", 1))
@@ -526,14 +535,18 @@ class MainArticlesPage(
     def search_url(self):
         return self.url + self.reverse_subpage("search")
 
+    @route(r"^sdilene/$", name="shared")
+    def shared(self, request):
+        return self.setup_article_page_context(request)
+
     @route(r"^search")
     def search(self, request):
         if request.method == "GET" and "q" in request.GET:
             query = request.GET["q"]
 
-            article_results = (
-                MainArticlePage.objects.live().order_by("-date").search(query)[:11]
-            )
+            article_results = self.append_all_shared_articles(
+                MainArticlePage.objects.live()
+            ).search(query)[:11]
 
             return render(
                 request,
@@ -616,6 +629,7 @@ class MainArticlePage(
         help_text="Kraj, ke kterému se článek vztahuje",
     )
     tags = ClusterTaggableManager(through=MainArticleTag, blank=True)
+    shared_tags = ClusterTaggableManager(through=SharedTaggedMainArticle, blank=True)
 
     search_fields = Page.search_fields + [
         index.SearchField("title"),
@@ -633,6 +647,7 @@ class MainArticlePage(
         FieldPanel("is_black"),
         FieldPanel("region"),
         FieldPanel("tags"),
+        FieldPanel("shared_tags"),
     ]
 
     promote_panels = make_promote_panels(
diff --git a/shared/models.py b/shared/models.py
index c4e7c0cb95346a32241692cfaecbd1302ae5e4d6..8082e2cc9988ed0865e72b74e4465afd08181b77 100644
--- a/shared/models.py
+++ b/shared/models.py
@@ -1,10 +1,11 @@
-import json
 import logging
+import sys
 
 from django.db import models
-from django.http import HttpResponse
+from django.db.models.functions import Coalesce
 from django.utils import timezone
-from requests import request
+from modelcluster.fields import ParentalKey, ParentalManyToManyField
+from taggit.models import ItemBase, TagBase
 from wagtail.admin.panels import FieldPanel, MultiFieldPanel, PublishingPanel
 from wagtail.fields import StreamField
 from wagtail.models import Page
@@ -215,3 +216,124 @@ class FooterMixin(models.Model):
 
     class Meta:
         abstract = True
+
+
+class SharedTag(TagBase):
+    class Meta:
+        verbose_name = "sdílený tag"
+        verbose_name_plural = "sdílené tagy"
+
+
+class SharedTaggedDistrictArticle(ItemBase):
+    tag = models.ForeignKey(
+        SharedTag, related_name="shared_district_tags", on_delete=models.CASCADE
+    )
+    content_object = ParentalKey(
+        to="district.DistrictArticlePage",
+        on_delete=models.CASCADE,
+        related_name="shared_district_articles",
+    )
+
+
+class SharedTaggedUniwebArticle(ItemBase):
+    tag = models.ForeignKey(
+        SharedTag, related_name="shared_uniweb_tags", on_delete=models.CASCADE
+    )
+    content_object = ParentalKey(
+        to="uniweb.UniwebArticlePage",
+        on_delete=models.CASCADE,
+        related_name="shared_uniweb_articles",
+    )
+
+
+class SharedTaggedMainArticle(ItemBase):
+    tag = models.ForeignKey(
+        SharedTag, related_name="shared_main_tags", on_delete=models.CASCADE
+    )
+    content_object = ParentalKey(
+        to="main.MainArticlePage",
+        on_delete=models.CASCADE,
+        related_name="shared_main_articles",
+    )
+
+
+class ArticlesMixin(models.Model):
+    shared_tags = ParentalManyToManyField(
+        "shared.SharedTag", related_name="Sdílené tagy", blank=True
+    )
+
+    content_panels = Page.content_panels + [FieldPanel("shared_tags")]
+
+    def append_all_shared_articles(self, previous_query):
+        districtArticleQuery: models.QuerySet = getattr(
+            sys.modules["district"], "DistrictArticlePage"
+        ).objects
+        uniwebArticlePageQuery: models.QuerySet = getattr(
+            sys.modules["uniweb"], "UniwebArticlePage"
+        ).objects
+        mainArticlePageQuery: models.QuerySet = getattr(
+            sys.modules["main"], "MainArticlePage"
+        ).objects
+
+        district_by_slug = (
+            districtArticleQuery.live()
+            .specific()
+            .filter(
+                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            )
+        )
+        uniweb_by_slug = (
+            uniwebArticlePageQuery.live()
+            .specific()
+            .filter(
+                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            )
+        )
+        main_by_slug = (
+            mainArticlePageQuery.live()
+            .specific()
+            .filter(
+                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            )
+        )
+
+        ordered_articles = (
+            district_by_slug.union(uniweb_by_slug)
+            .union(main_by_slug)
+            .union(previous_query)
+            .distinct()
+            .annotate(
+                article_date=Coalesce(
+                    "-districtarticlepage__date", "-uniwebarticlepage__date", "-date"
+                ),
+                shared=True,
+            )
+            .order_by("aritcle_date")
+        )
+
+        return ordered_articles
+
+    def get_article_page_by_slug(self, slug: str):
+        articles = self.append_all_shared_articles()
+        return articles.filter(page_ptr__path=slug).first()
+
+    def setup_article_page_context(self, request):
+        slug = request.GET.get("slug", "")
+        return self.get_article_page_by_slug(slug).serve(request)
+
+    def get_search_tags_params(self, site_article_ids: list):
+        return {
+            "districtarticlepage__id__in": site_article_ids,
+            "uniwebarticlepage__id__in": site_article_ids,
+            "mainarticlepage__id__in": site_article_ids,
+        }
+
+    def get_search_article_params_by_tags(self, tag: str):
+        return {
+            "uniwebarticlepage__tags__name": tag,
+            "districtarticlepage__tags__name": tag,
+            "mainarticlepage__tags__name": tag,
+        }
+
+    class Meta:
+        abstract = True
diff --git a/uniweb/models.py b/uniweb/models.py
index 13718a586eddf97d7ccfc9897787e7a1d6646fc3..520635a14180541dea00b5b5d6c298969682bcc1 100644
--- a/uniweb/models.py
+++ b/uniweb/models.py
@@ -7,7 +7,7 @@ from django.db import models
 from django.utils.translation import gettext_lazy
 from modelcluster.contrib.taggit import ClusterTaggableManager
 from modelcluster.fields import ParentalKey
-from taggit.models import TaggedItemBase
+from taggit.models import Tag, TaggedItemBase
 from wagtail import blocks
 from wagtail.admin.panels import (
     FieldPanel,
@@ -19,6 +19,7 @@ from wagtail.admin.panels import (
 )
 from wagtail.contrib.forms.models import AbstractForm, AbstractFormField
 from wagtail.contrib.forms.panels import FormSubmissionsPanel
+from wagtail.contrib.routable_page.models import RoutablePageMixin, route
 from wagtail.contrib.table_block.blocks import TableBlock
 from wagtail.fields import RichTextField, StreamField
 from wagtail.images.blocks import ImageChooserBlock
@@ -30,9 +31,11 @@ from shared.blocks import ChartBlock, NewsletterSubscriptionBlock
 from shared.const import RICH_TEXT_DEFAULT_FEATURES
 from shared.models import (
     ArticleMixin,
+    ArticlesMixin,
     ExtendedMetadataHomePageMixin,
     ExtendedMetadataPageMixin,
     FooterMixin,
+    SharedTaggedUniwebArticle,
     SubpageMixin,
 )
 from shared.utils import make_promote_panels, strip_all_html_tags, trim_to_length
@@ -509,12 +512,19 @@ class UniwebCalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page):
 
 
 class UniwebArticlesIndexPage(
-    Page, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin
+    RoutablePageMixin,
+    Page,
+    ExtendedMetadataPageMixin,
+    SubpageMixin,
+    MetadataPageMixin,
+    ArticlesMixin,
 ):
     ### FIELDS
 
     ### PANELS
 
+    content_panels = ArticlesMixin.content_panels
+
     promote_panels = make_promote_panels()
 
     settings_panels = []
@@ -526,6 +536,10 @@ class UniwebArticlesIndexPage(
 
     ### OTHERS
 
+    @route(r"^sdilene/$", name="shared")
+    def shared(self, request):
+        return self.setup_article_page_context(request)
+
     class Meta:
         verbose_name = "Sekce článků"
 
@@ -534,15 +548,16 @@ class UniwebArticlesIndexPage(
         num = request.GET.get("page")
         tag = request.GET.get("tag")
 
-        articles = (
-            self.get_children().live().specific().order_by("-uniwebarticlepage__date")
+        articles = self.append_all_shared_articles(
+            self.get_children().live().specific()
         )
+
         if tag is not None:
-            articles = articles.filter(uniwebarticlepage__tags__name=tag)
+            articles = articles.filter(**self.get_search_article_params_by_tags(tag))
 
         context["articles"] = Paginator(articles, ARTICLES_PER_PAGE).get_page(num)
-        context["tags"] = UniwebArticleTag.tags_for(UniwebArticlePage).filter(
-            uniwebarticlepage__in=articles
+        context["tags"] = Tag.objects.filter(
+            **self.get_search_tags_params(articles.values_list("id", flat=True))
         )
         context["active_tag"] = tag
         return context
@@ -554,10 +569,14 @@ class UniwebArticlePage(
     ### FIELDS
 
     tags = ClusterTaggableManager(through=UniwebArticleTag, blank=True)
+    shared_tags = ClusterTaggableManager(through=SharedTaggedUniwebArticle, blank=True)
 
     ### PANELS
 
-    content_panels = ArticleMixin.content_panels + [FieldPanel("tags")]
+    content_panels = ArticleMixin.content_panels + [
+        FieldPanel("tags"),
+        FieldPanel("shared_tags"),
+    ]
 
     promote_panels = make_promote_panels(
         admin_help.build(admin_help.NO_SEO_TITLE, admin_help.NO_DESCRIPTION_USE_PEREX),