diff --git a/.tugboat/config.yml b/.tugboat/config.yml
deleted file mode 100644
index b403657a27682c9d949e30d548a1a511b5c68d5f..0000000000000000000000000000000000000000
--- a/.tugboat/config.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-services:
-  php:
-    image: q0rban/tugboat-drupal:9.4
-    default: true
-    http: false
-    depends: mysql
-    commands:
-      update: |
-        set -eux
-        # Check out a branch using the unique Tugboat ID for this repository, to
-        # ensure we don't clobber an existing branch.
-        git checkout -b $TUGBOAT_REPO_ID
-        # Composer is hungry. You need a Tugboat project with a pretty sizeable
-        # chunk of memory.
-        export COMPOSER_MEMORY_LIMIT=-1
-        # This is an environment variable we added in the Dockerfile that
-        # provides the path to Drupal composer root (not the web root).
-        cd $DRUPAL_COMPOSER_ROOT
-        # We configure the Drupal project to use the checkout of the module as a
-        # Composer package repository.
-        composer config repositories.tugboat vcs $TUGBOAT_ROOT
-        # Now we can require this module, specifing the branch name we created
-        # above that uses the $TUGBOAT_REPO_ID environment variable.
-        composer require drupal/photoswipe:dev-$TUGBOAT_REPO_ID
-        # Install Drupal on the site.
-        vendor/bin/drush \
-          --yes \
-          --db-url=mysql://tugboat:tugboat@mysql:3306/tugboat \
-          --site-name="Live preview for ${TUGBOAT_PREVIEW_NAME}" \
-          --account-pass=admin \
-          site:install standard
-        # Set up the files directory permissions.
-        mkdir -p $DRUPAL_DOCROOT/sites/default/files
-        chgrp -R www-data $DRUPAL_DOCROOT/sites/default/files
-        chmod 2775 $DRUPAL_DOCROOT/sites/default/files
-        chmod -R g+w $DRUPAL_DOCROOT/sites/default/files
-        # Enable the module.
-        vendor/bin/drush --yes pm:enable photoswipe
-      build: |
-        set -eux
-        # Delete and re-check out this branch in case this is built from a Base Preview.
-        git branch -D $TUGBOAT_REPO_ID && git checkout -b $TUGBOAT_REPO_ID || true
-        export COMPOSER_MEMORY_LIMIT=-1
-        cd $DRUPAL_COMPOSER_ROOT
-        composer install --optimize-autoloader
-        # Update this module, including all dependencies.
-        composer update drupal/photoswipe --with-all-dependencies
-        vendor/bin/drush --yes updb
-        vendor/bin/drush cache:rebuild
-  mysql:
-    image: tugboatqa/mariadb
diff --git a/README.md b/README.md
index 6e1fefee2f429ca7c094fe45dbaeaffe17f9d0a6..f8219cd80367860904fb335b2c3cbb34e5b6c0ad 100644
--- a/README.md
+++ b/README.md
@@ -14,20 +14,20 @@ browsing features (in particular swiping to the next picture)!
 
 - Require the module, e.g. via composer: "composer require drupal/photoswipe"
 - Install the module
-- Download the "PhotoSwipe-4.1.3" zip file
-- Unzip and place the contents of the unzipped "PhotoSwipe-4.1.3" folder
+- Download the "PhotoSwipe-5.3.1" zip file
+- Unzip and place the contents of the unzipped "PhotoSwipe-5.3.1" folder
 into "library/photoswipe" folder so that the folder structure is:
 "library/photoswipe/dist/photoswipe.js"
 - Check the status report for errors
 
-### Alternative composer installation
+### Alternative composer installation (recommended)
 
 - Require the module, e.g. via composer: "composer require drupal/photoswipe"
 - Install the module
 - Enable usage of third-party libraries using composer, see
 [here](https://www.drupal.org/docs/develop/using-composer/manage-dependencies#third-party-libraries) for an explanation.
 - Require the photoswipe library using
-"composer require bower-asset/photoswipe:^4"
+`composer require bower-asset/photoswipe:^5.3`
 - Check your status report
 
 Then simply configure your image fields to use photoswipe as their field display
diff --git a/config/install/photoswipe.settings.yml b/config/install/photoswipe.settings.yml
index d986d6c702954dd6d01ed0f1593bfdade05be94a..3141d7023965aaf4e80214ad7a46581f9a6b56eb 100644
--- a/config/install/photoswipe.settings.yml
+++ b/config/install/photoswipe.settings.yml
@@ -1,28 +1,21 @@
 photoswipe_always_load_non_admin: false
+enable_cdn: false
 # @see http://photoswipe.com/documentation/options.html
 options:
-#  index: 0
   showAnimationDuration:  333
   hideAnimationDuration:  333
-  showHideOpacity:  FALSE
   bgOpacity:  1
   spacing:  0.12
   allowPanToNext:  TRUE
-  maxSpreadZoom:  2
+  maxZoomLevel:  2
   loop:  TRUE
   pinchToClose:  TRUE
-  closeOnScroll:  TRUE
   closeOnVerticalDrag:  TRUE
-  mouseUsed:  FALSE
   escKey:  TRUE
   arrowKeys:  TRUE
-  history:  TRUE
-#  galleryUID: 1
-#  galleryPIDs: FALSE
   errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>'
   preload:
     - 1
     - 1
   mainClass:  NULL
-  focus:  TRUE
   modal: TRUE
diff --git a/config/schema/photoswipe.schema.yml b/config/schema/photoswipe.schema.yml
index 4bbc728e7a7411792448341ec0bc2ff535a25933..b7e1d90c997dfdced52988f4b0cadcd78408f15e 100644
--- a/config/schema/photoswipe.schema.yml
+++ b/config/schema/photoswipe.schema.yml
@@ -4,20 +4,19 @@ photoswipe.settings:
   mapping:
     photoswipe_always_load_non_admin:
       type: boolean
+    enable_cdn:
+      label: 'Indicates if CDN is enabled.'
+      type: boolean
     options:
       type: mapping
       label: Options
       mapping:
-#      index: integer
         showAnimationDuration:
           type: integer
           label: 'Show Animation Duration'
         hideAnimationDuration:
           type: integer
           label: 'Hide Animation Duration'
-        showHideOpacity:
-          type: boolean
-          label: 'Show Hide Opacity'
         bgOpacity:
           type: float
           label: 'Background Opacity'
@@ -27,35 +26,24 @@ photoswipe.settings:
         allowPanToNext:
           type: boolean
           label: 'Allow Pan to Next'
-        maxSpreadZoom:
+        maxZoomLevel:
           type: integer
-          label: 'Max Spread Zoom'
+          label: 'Max Zoom Level'
         loop:
           type: boolean
           label: 'Loop'
         pinchToClose:
           type: boolean
           label: 'Pinch to Close'
-        closeOnScroll:
-          type: boolean
-          label: 'Close on Scroll'
         closeOnVerticalDrag:
           type: boolean
           label: 'Close on Vertical Drag'
-        mouseUsed:
-          type: boolean
-          label: 'Mouse Used'
         escKey:
           type: boolean
           label: 'Escape Key'
         arrowKeys:
           type: boolean
           label: 'Arrow Keys'
-        history:
-          type: boolean
-          label: 'History'
-  #      galleryUID: Integer
-  #      galleryPIDs: Boolean
         errorMsg:
           type: string
           label: 'Error Message'
@@ -67,9 +55,6 @@ photoswipe.settings:
         mainClass:
           type: string
           label: 'Main Class'
-        focus:
-          type: boolean
-          label: 'Focus'
         modal:
           type: boolean
           label: 'Modal'
diff --git a/drupalci.yml b/drupalci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..80262bdb61d812fd7e7e80aa67bfe10150516b4c
--- /dev/null
+++ b/drupalci.yml
@@ -0,0 +1,40 @@
+# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
+_phpunit_testgroups_to_execute: &testgroups
+  testgroups: '--all'
+
+build:
+  assessment:
+    testing:
+      container_command:
+        # Install photoswipe library locally.
+        commands: >
+          cd ${SOURCE_DIR}; \
+          sudo -u www-data composer config --no-plugins allow-plugins.oomphinc/composer-installers-extender true; \
+          sudo -u www-data composer require oomphinc/composer-installers-extender; \
+          sudo -u www-data composer config --json "extra.installer-paths.web/libraries/{\$name}" '["type:drupal-library", "type:npm-asset", "type:bower-asset"]'; \
+          sudo -u www-data composer config repositories.assets '{ "type": "composer", "url": "https://asset-packagist.org" }'; \
+          sudo -u www-data composer require "bower-asset/photoswipe:5.3.7"; \
+          sudo -u www-data composer require "npm-asset/photoswipe-dynamic-caption-plugin:1.2"
+      run_tests.kernel:
+        types: 'PHPUnit-Kernel'
+        suppress-deprecations: false
+        halt-on-fail: false
+        <<: *testgroups
+      run_tests.functional:
+        types: 'PHPUnit-Functional'
+        suppress-deprecations: false
+        halt-on-fail: false
+        <<: *testgroups
+      run_tests.build:
+        # Limit concurrency due to disk space concerns.
+        concurrency: 15
+        types: 'PHPUnit-Build'
+        suppress-deprecations: false
+        halt-on-fail: false
+        <<: *testgroups
+      run_tests.javascript:
+        concurrency: 15
+        types: 'PHPUnit-FunctionalJavascript'
+        suppress-deprecations: false
+        halt-on-fail: false
+        <<: *testgroups
diff --git a/js/photoswipe.jquery.js b/js/photoswipe.jquery.js
deleted file mode 100644
index 7c1b66684e91264f49e14e35972bfa1e78319852..0000000000000000000000000000000000000000
--- a/js/photoswipe.jquery.js
+++ /dev/null
@@ -1,175 +0,0 @@
-(function ($, Drupal, PhotoSwipe, PhotoSwipeUI_Default) {
-  Drupal.behaviors.photoswipe = {
-    /**
-     * PhotoSwipe Options, coming from Drupal.settings.
-     */
-    photoSwipeOptions: {},
-    /**
-     * Instantiated galleries.
-     */
-    galleries: [],
-    /**
-     * Load PhotoSwipe once page is ready
-     */
-    attach: function (context, settings) {
-      this.photoSwipeOptions = settings.photoswipe ? settings.photoswipe.options : {};
-
-      // First ensure all photoswipe photos are in a photoswipe-gallery wrapper:
-      var $imagesWithoutGalleries = $('a.photoswipe', context).filter(function (elem) {
-        return !$(this).parents('.photoswipe-gallery').length;
-      });
-      if ($imagesWithoutGalleries.length) {
-        // We have no galleries just individual images.
-        $imagesWithoutGalleries.each(function (index) {
-          $imageLink = $(this);
-          // Add the wrapper and indicate that it's an automatic fallback:
-          $imageLink.wrap('<span class="photoswipe-gallery photoswipe-gallery--fallback-wrapper"></span>');
-        });
-      }
-
-      var $galleries = $('.photoswipe-gallery', context);
-      if ($galleries.length) {
-        // if body haven't special container for show photoswipe gallery append it.
-        if (settings.photoswipe && 'container' in settings.photoswipe && !$('.pswp').length) {
-          $('body').append(settings.photoswipe.container);
-        }
-
-        // loop through all gallery elements and bind events
-        $(once('photoswipe', $galleries)).each(function (index) {
-          var $gallery = $(this);
-          $gallery.attr('data-pswp-uid', index + 1);
-          // Definitely prevent doble event binding on AJAX
-          $gallery.off('click', Drupal.behaviors.photoswipe.onThumbnailsClick);
-          $gallery.on('click', Drupal.behaviors.photoswipe.onThumbnailsClick);
-        });
-      }
-
-      // Parse URL and open gallery if it contains #&pid=3&gid=1
-      var hashData = this.parseHash();
-      if (hashData.pid > 0 && hashData.gid > 0) {
-        this.openPhotoSwipe(hashData.pid - 1, $($galleries[hashData.gid - 1]));
-      }
-    },
-    /**
-     * Triggers when user clicks on thumbnail.
-     *
-     * Code taken from http://photoswipe.com/documentation/getting-started.html
-     * and adjusted accordingly.
-     */
-    onThumbnailsClick: function (e) {
-      e = e || window.event;
-      var $clickedGallery = $(this);
-      var eTarget = e.target || e.srcElement;
-      var $eTarget = $(eTarget);
-
-      // find root element of slide
-      var $clickedListItem = $eTarget.closest('.photoswipe');
-      if (!$clickedListItem) {
-        return;
-      }
-
-      // get the index of the clicked element
-      var index = $clickedGallery.find('.photoswipe').index($clickedListItem);
-      if (index >= 0) {
-        e.preventDefault ? e.preventDefault() : e.returnValue = false;
-        // open PhotoSwipe if valid index found
-        Drupal.behaviors.photoswipe.openPhotoSwipe(index, $clickedGallery);
-        // Only prevent default when clicking on a photoswipe image.
-        return false;
-      }
-    },
-    /**
-     * Code taken from http://photoswipe.com/documentation/getting-started.html
-     * and adjusted accordingly.
-     */
-    openPhotoSwipe: function (index, galleryElement, options) {
-      var pswpContainer = $('.pswp')[0];
-      if (!pswpContainer) {
-        throw "The photoswipe container is required, but missing on this page. Can not open photoswipe.";
-      }
-      var items = [];
-      options = options || Drupal.behaviors.photoswipe.photoSwipeOptions;
-
-      var images = galleryElement.find('a.photoswipe');
-      images.each(function (index) {
-        var $image = $(this);
-        var size = $image.data('size') ? $image.data('size').split('x') : ['', ''];
-        items.push(
-          {
-            src: $image.attr('href'),
-            w: size[0],
-            h: size[1],
-            title: $image.data('overlay-title'),
-            msrc: $image.find('img').attr('src')
-          }
-        );
-      })
-
-      // define options
-      options.index = index;
-      // define gallery index (for URL)
-      options.galleryUID = galleryElement.data('pswp-uid');
-
-      // Add zoom animation function:
-      options.getThumbBoundsFn = function (index) {
-        var tn = galleryElement.find('a.photoswipe:eq(' + index + ') img');
-        if (tn.length == 0) {
-          tn = galleryElement.find('a.photoswipe:eq(0) img');
-          if (tn.length == 0) {
-            // Return undefined if still null, see https://www.drupal.org/project/photoswipe/issues/3023442
-            return undefined;
-          }
-        }
-        var tw = tn.width();
-        var tpos = tn.offset();
-        return { x: tpos.left, y: tpos.top, w: tw };
-      }
-
-      // Ensures we have items (.photoswipe element) before initializing
-      // PhotoSwipe so to make PhotoSwipe get along with Blazy, Slick, etc.
-      if (items.length > 0) {
-        // Pass data to PhotoSwipe and initialize it
-        var gallery = new PhotoSwipe(pswpContainer, PhotoSwipeUI_Default, items, options);
-        gallery.init();
-        this.galleries.push(gallery);
-      }
-    },
-    /**
-     * Parse picture index and gallery index from URL (#&pid=1&gid=2)
-     *
-     * Code taken from http://photoswipe.com/documentation/getting-started.html
-     * and adjusted accordingly.
-     */
-    parseHash: function () {
-      var hash = window.location.hash.substring(1),
-        params = {};
-
-      if (hash.length < 5) {
-        return params;
-      }
-
-      var vars = hash.split('&');
-      for (var i = 0; i < vars.length; i++) {
-        if (!vars[i]) {
-          continue;
-        }
-        var pair = vars[i].split('=');
-        if (pair.length < 2) {
-          continue;
-        }
-        params[pair[0]] = pair[1];
-      }
-
-      if (params.gid) {
-        params.gid = parseInt(params.gid, 10);
-      }
-
-      if (!params.hasOwnProperty('pid')) {
-        return params;
-      }
-      params.pid = parseInt(params.pid, 10);
-
-      return params;
-    }
-  };
-})(jQuery, Drupal, PhotoSwipe, PhotoSwipeUI_Default);
diff --git a/js/photoswipe.js b/js/photoswipe.js
new file mode 100644
index 0000000000000000000000000000000000000000..f50472ae61bb7d9e6dab845e8eae2adea9685147
--- /dev/null
+++ b/js/photoswipe.js
@@ -0,0 +1,38 @@
+(function (Drupal, PhotoSwipeLightbox) {
+  /**
+   * Initialises photoswipe galleries.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.photoswipe = {
+
+    /**
+     * Initialize photoswipe galleries.
+     */
+    attach: function (context, settings) {
+      once('photoswipe', '.photoswipe-gallery', context).forEach((gallery) => {
+        const lightbox = new PhotoSwipeLightbox({
+          // Select the gallery.
+          gallerySelector: '.photoswipe-gallery',
+          // Elements within gallerySelector (slides).
+          childSelector: 'a',
+          // Include PhotoSwipe Core
+          pswpModule: PhotoSwipe,
+          ...settings?.photoswipe?.options || {}
+        });
+
+        // Adds ability to react on photoswipe initialization.
+        const event = new CustomEvent('photoswipeLightboxBuild', {
+          detail: {
+            lightbox,
+          }
+        });
+
+        gallery.dispatchEvent(event);
+
+        // Initialize.
+        lightbox.init();
+      });
+    },
+  };
+})(Drupal, PhotoSwipeLightbox);
diff --git a/js/prepare-galleries.js b/js/prepare-galleries.js
new file mode 100644
index 0000000000000000000000000000000000000000..7977cb389d9712b7a72252ca475b22ebb8ed369d
--- /dev/null
+++ b/js/prepare-galleries.js
@@ -0,0 +1,20 @@
+(function (Drupal) {
+  /**
+   * Adds wrapper for images without it.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.photoswipePrepareGalleries = {
+
+    /**
+     * Ensure all photoswipe photos are in a photoswipe-gallery wrapper.
+     */
+    attach: function (context) {
+      once('photoswipePrepareGalleries', 'a.photoswipe', context).forEach(element => {
+        if (!element.closest('.photoswipe-gallery')) {
+          element.outerHTML = `<span class="photoswipe-gallery photoswipe-gallery--fallback-wrapper">${element.outerHTML}</span>`;
+        }
+      })
+    },
+  };
+})(Drupal);
diff --git a/modules/photoswipe_caption/README.md b/modules/photoswipe_caption/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb321cdf6f34196370698352f880001a0166647d
--- /dev/null
+++ b/modules/photoswipe_caption/README.md
@@ -0,0 +1,27 @@
+# Provides integration with photoswipe caption plugin.
+Starting from [v5](https://photoswipe.com/caption/) of photoswipe there is separate plugin for captions.
+
+# Installation
+
+## (optional) Dependencies installation
+
+This is optional step because we already provide CDN libraries integration, so it should work without local installation.
+
+### Composer installation (recommended)
+
+- Enable usage of third-party libraries using composer, see
+  [here](https://www.drupal.org/docs/develop/using-composer/manage-dependencies#third-party-libraries) for an explanation.
+- Install caption library using following composer command: <br>
+  `composer require "npm-asset/photoswipe-dynamic-caption-plugin:^1.2"`
+- Check your status report
+
+### Manual Installation
+
+- Clone https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin repository into libraries folder.
+- Check the status report for errors
+
+### Configuration.
+
+The module has predefined hardcoded [configuration](./config/install/photoswipe_caption.settings.yml). There are two possible ways of altering configuration:
+1. (recommended) Using `hook_photoswipe_js_options_alter` simply put custom option in `$settings['captionOptions']`. Check `photoswipe_caption_photoswipe_js_options_alter` for details.
+2. Override manually configuration file and import it.
diff --git a/modules/photoswipe_caption/config/install/photoswipe_caption.settings.yml b/modules/photoswipe_caption/config/install/photoswipe_caption.settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a66457ef1e714fdb5127baa946df33131ad98901
--- /dev/null
+++ b/modules/photoswipe_caption/config/install/photoswipe_caption.settings.yml
@@ -0,0 +1,6 @@
+options:
+  type: 'auto'
+  mobileLayoutBreakpoint: 600
+  horizontalEdgeThreshold: 20
+  mobileCaptionOverlapRatio: 0.3
+  verticallyCenterImage: false
diff --git a/modules/photoswipe_caption/config/schema/photoswipe_caption.schema.yml b/modules/photoswipe_caption/config/schema/photoswipe_caption.schema.yml
new file mode 100644
index 0000000000000000000000000000000000000000..912331ce043fd1c4fa578349fc9f2dbc2a8f2a41
--- /dev/null
+++ b/modules/photoswipe_caption/config/schema/photoswipe_caption.schema.yml
@@ -0,0 +1,23 @@
+photoswipe_caption.settings:
+  type: config_object
+  label: Settings
+  mapping:
+    options:
+      type: mapping
+      label: Caption options
+      mapping:
+        type:
+          type: string
+          label: 'Type'
+        mobileLayoutBreakpoint:
+          type: integer
+          label: 'Maximum window width at which mobile layout should be used'
+        horizontalEdgeThreshold:
+          type: integer
+          label: 'Horizontal edge threshold'
+        mobileCaptionOverlapRatio:
+          type: float
+          label: 'Mobile caption overlap ratio'
+        verticallyCenterImage:
+          type: boolean
+          label: 'Vertically center image'
diff --git a/modules/photoswipe_caption/js/photoswipe_caption.js b/modules/photoswipe_caption/js/photoswipe_caption.js
new file mode 100644
index 0000000000000000000000000000000000000000..503b4e9004d7abb12f7775d98613b7d35869d4a0
--- /dev/null
+++ b/modules/photoswipe_caption/js/photoswipe_caption.js
@@ -0,0 +1,33 @@
+(function (Drupal, PhotoSwipeDynamicCaption) {
+  /**
+   * Adds caption plugin to the photoswipe.
+   *
+   * @type {Drupal~behavior}
+   */
+  Drupal.behaviors.photoswipeCaption = {
+
+    /**
+     * Adds caption plugin to the photoswipe.
+     */
+    attach: function (context, settings) {
+      const captionOptions = settings?.photoswipe?.options?.captionOptions || {};
+      // Remove caption related options from photoswipe options.
+      delete settings?.photoswipe?.options?.captionOptions;
+
+      // Attaches caption plugin.
+      once('photoswipeCaption', '.photoswipe-gallery', context).forEach(gallery => {
+        gallery.addEventListener('photoswipeLightboxBuild', e => {
+          const lightbox = e.detail.lightbox;
+
+          new PhotoSwipeDynamicCaption(lightbox, {
+            captionContent: (slide) => {
+              return slide.data.element.getAttribute('data-overlay-title');
+            },
+            ...captionOptions,
+          });
+
+        });
+      })
+    },
+  };
+})(Drupal, PhotoSwipeDynamicCaption);
diff --git a/modules/photoswipe_caption/photoswipe_caption.info.yml b/modules/photoswipe_caption/photoswipe_caption.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d0eba54b2a9208c77b625d6201114bf3d0a6ba83
--- /dev/null
+++ b/modules/photoswipe_caption/photoswipe_caption.info.yml
@@ -0,0 +1,7 @@
+name: PhotoSwipe Caption
+description: 'Addon with photoswipe Caption plugin'
+core_version_requirement: ^9.3 || ^10
+type: module
+package: Media
+dependencies:
+  - photoswipe:photoswipe
diff --git a/modules/photoswipe_caption/photoswipe_caption.install b/modules/photoswipe_caption/photoswipe_caption.install
new file mode 100644
index 0000000000000000000000000000000000000000..012832577eb4d84cc1fe1849bf54853c1f1101f1
--- /dev/null
+++ b/modules/photoswipe_caption/photoswipe_caption.install
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Install, uninstall and update hooks for Photswipe module.
+ */
+
+use Drupal\Core\Link;
+
+/**
+ * Implements hook_requirements().
+ */
+function photoswipe_caption_requirements($phase) {
+  $requirements = [];
+  // If we are not in runtime phase, there is nothing to do. So bail out early.
+  if ($phase !== 'runtime') {
+    return [];
+  }
+
+  $is_cdn_enabled = \Drupal::config('photoswipe.settings')->get('enable_cdn');
+  $library_file_finder = \Drupal::service('library.libraries_directory_file_finder');
+  $library_discovery = \Drupal::service('library.discovery');
+
+  // Init.
+  $library_definition = $library_discovery->getLibraryByName('photoswipe_caption', 'photoswipe_caption.cdn');
+
+  // We only care about major version.
+  $version_required = 1;
+
+  $library_req = REQUIREMENT_OK;
+  $help_text = t('
+  <h6> Composer installation (recommended) </h6>
+
+<p>
+  - Enable usage of third-party libraries using composer, see [here](https://www.drupal.org/docs/develop/using-composer/manage-dependencies#third-party-libraries) for an explanation. </br>
+  - Install caption library using following composer command: `composer require "npm-asset/photoswipe-dynamic-caption-plugin:^1.2"` </br>
+  - Check your status report
+</p>
+
+ <h6> Manual Installation </h6>
+  - Clone https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin repository into libraries folder. </br>
+  - Check the status report for errors </br>
+</p>
+');
+
+  if (!$is_cdn_enabled) {
+    $library_req = REQUIREMENT_ERROR;
+    $message = t('<strong>Photoswipe library not found, and is CDN is disabled!</strong>');
+    $description = t('You can either enable CDN fetching in configuration (here is @link) and library will be fetched automatically or install it locally. Here is instruction:<br>@helptext',
+      [
+        '@helptext' => $help_text,
+        '@link' => Link::createFromRoute(t('link'), 'photoswipe.admin_settings')->toString(),
+      ]
+    );
+  }
+  // Library from CDN. Show a warning.
+  elseif (!$library_file_finder->find('photoswipe-dynamic-caption-plugin')) {
+    $library_version = $library_definition['version'];
+    $library_req = REQUIREMENT_WARNING;
+    $message = t('Missing local library. CDN version: @version', ['@version' => $library_version]);
+    $description = t('Library not found in the "libraries" directory.
+     You are using the library from the fallback CDN defined in libraries.yml,
+     but local libraries are preferred over CDN. @helptext', ['@helptext' => $help_text]);
+  }
+  elseif (!$library_file_finder->find('photoswipe-dynamic-caption-plugin/package.json')) {
+    $library_req = REQUIREMENT_ERROR;
+    $message = t('<strong>Photoswipe caption library found, but missing photoswipe.json detected!</strong>');
+    $description = t('Local library folder found, but library seems to be corrupted, please require the photoswipe caption library correctly!', ['@helptext' => $help_text]);
+  }
+  else {
+    $photoswipe_json_content = file_get_contents($library_file_finder->find('photoswipe-dynamic-caption-plugin/package.json'));
+    $photoswipe_json = json_decode($photoswipe_json_content, TRUE);
+    $library_version = $photoswipe_json['version'];
+    // We only care about major version.
+    [$major] = explode('.', $library_version);
+    if ((int) $major !== $version_required) {
+      $library_req = REQUIREMENT_ERROR;
+      $message = t('Current version supports ^@version.x: Please install appropriate version', [
+        '@version' => $version_required,
+      ]);
+      $description = t('<strong>You need to install a compatible version!</strong><br><br>@helptext', ['@helptext' => $help_text]);
+    }
+  }
+
+  // Requirements.
+  $requirements['photoswipe_caption'] = [
+    'title' => t('Photoswipe Caption plugin'),
+    'severity' => $library_req,
+    'value' => $message ?: $version_required,
+    'description' => $description ?: '',
+  ];
+
+  return $requirements;
+}
diff --git a/modules/photoswipe_caption/photoswipe_caption.libraries.yml b/modules/photoswipe_caption/photoswipe_caption.libraries.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0d213a1d066fcc654f81763db4c2cfbcc74e96e9
--- /dev/null
+++ b/modules/photoswipe_caption/photoswipe_caption.libraries.yml
@@ -0,0 +1,30 @@
+photoswipe_caption.cdn:
+  remote: https://github.com/dimsemenov/PhotoSwipe/archive/v5.3.7.zip
+  version: 1.2.7
+  license:
+    name: MIT
+    gpl-compatible: false
+  css:
+    component:
+      //unpkg.com/photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css: { external: true }
+  js:
+    //unpkg.com/photoswipe-dynamic-caption-plugin@1.2.7/dist/photoswipe-dynamic-caption-plugin.umd.min.js: { external: true }
+
+photoswipe_caption.local:
+  version: VERSION
+  license:
+    name: MIT
+    gpl-compatible: false
+  css:
+    component:
+      /libraries/photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css: { }
+  js:
+    /libraries/photoswipe-dynamic-caption-plugin/dist/photoswipe-dynamic-caption-plugin.umd.min.js: { minified: true }
+
+photoswipe_caption.init:
+  js:
+    js/photoswipe_caption.js: {  }
+  dependencies:
+    - core/drupal
+    - core/once
+    - core/drupalSettings
diff --git a/modules/photoswipe_caption/photoswipe_caption.module b/modules/photoswipe_caption/photoswipe_caption.module
new file mode 100644
index 0000000000000000000000000000000000000000..11c4d4c6fe8fdcd9483ac212691ce250076a5eca
--- /dev/null
+++ b/modules/photoswipe_caption/photoswipe_caption.module
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Container photoswipe_caption hooks.
+ */
+
+/**
+ * Implements hook_library_info_alter().
+ */
+function photoswipe_caption_library_info_alter(&$libraries, $module) {
+  switch ($module) {
+    // We need this because we want react on event that is triggered when
+    // photoswipe is initialized.
+    case 'photoswipe':
+      $libraries['photoswipe.init']['dependencies'][] = 'photoswipe_caption/photoswipe_caption.init';
+      return;
+
+    // If local library is provided take it, otherwise check if CDN is
+    // enabled and if so get library from CDN.
+    case 'photoswipe_caption':
+      $library_file_finder = \Drupal::service('library.libraries_directory_file_finder');
+      $is_local = (bool) $library_file_finder->find('photoswipe-dynamic-caption-plugin');
+
+      $is_cnd_enabled = \Drupal::config('enable_cdn')->get('enable_cdn');
+
+      $dependency = match(TRUE) {
+        $is_local => 'photoswipe_caption/photoswipe_caption.local',
+        $is_cnd_enabled => 'photoswipe_caption/photoswipe_caption.cdn',
+        default => NULL,
+      };
+
+      if ($dependency !== NULL) {
+        $libraries['photoswipe_caption.init']['dependencies'][] = $dependency;
+      }
+  }
+}
+
+/**
+ * Implements hook_photoswipe_js_options_alter().
+ */
+function photoswipe_caption_photoswipe_js_options_alter(array &$settings) {
+  $options = $settings['captionOptions'] ?? [];
+  // Make sure that users can override options.
+  $settings['captionOptions'] = $options + \Drupal::config('photoswipe_caption.settings')->get('options');
+}
diff --git a/modules/photoswipe_caption/tests/FunctionalJavascript/CaptionTest.php b/modules/photoswipe_caption/tests/FunctionalJavascript/CaptionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..811edbcb9fd469b2ea9baab03ef5d34d9c9107e4
--- /dev/null
+++ b/modules/photoswipe_caption/tests/FunctionalJavascript/CaptionTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Tests\photoswipe\FunctionalJavascript;
+
+/**
+ * Tests photoswipe caption module.
+ *
+ * @group photoswipe
+ */
+class CaptionTest extends PhotoswipeTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'test_page_test',
+    'file',
+    'image',
+    'node',
+    'field_ui',
+    'photoswipe',
+    'photoswipe_caption',
+  ];
+
+  /**
+   * Tests if caption is visible.
+   */
+  public function testPhotoswipeFieldFormatterOnNodeDisplay() {
+    $session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->createImageField('field_test', 'node', 'article', [
+      'uri_scheme' => 'public',
+      'required' => 'true',
+    ], ['alt_field_required' => 1]);
+    $this->drupalGet('node/add/article');
+    $page->fillField('title[0][value]', 'My test content');
+    $this->assertNotEmpty($image_upload_field = $page->find('css', '#edit-field-test-0-upload'));
+    $image_upload_field->attachFile($this->container->get('file_system')->realpath($this->getTestFiles('image')[0]->uri));
+    $session->waitForElementVisible('css', '.image-preview');
+
+    $alt_text = 'Alt text';
+    $page->fillField('Alternative text', $alt_text);
+    $page->pressButton('edit-submit');
+    $this->drupalGet('/node/1');
+
+    $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
+    // Open the photoswipe layer.
+    $this->getSession()->getPage()->find('css', 'a[href*="image-test.png"].photoswipe')->click();
+    $session->waitForElementVisible('css', '.pswp');
+    $session->elementTextEquals('css', '.pswp__dynamic-caption', $alt_text);
+  }
+
+}
diff --git a/photoswipe.install b/photoswipe.install
index 80374b60d15bb4821029fb3572469a47f2952321..9cd6ee4727897a0b0e9e4fad65607aa00b2f9914 100644
--- a/photoswipe.install
+++ b/photoswipe.install
@@ -5,6 +5,8 @@
  * Install, uninstall and update hooks for Photswipe module.
  */
 
+use Drupal\Core\Link;
+
 /**
  * Implements hook_requirements().
  */
@@ -15,6 +17,7 @@ function photoswipe_requirements($phase) {
     return [];
   }
 
+  $is_cdn_enabled = \Drupal::config('photoswipe.settings')->get('enable_cdn');
   /** @var \Drupal\Core\Asset\LibrariesDirectoryFileFinder $library_file_finder */
   $library_file_finder = \Drupal::service('library.libraries_directory_file_finder');
   /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
@@ -23,7 +26,7 @@ function photoswipe_requirements($phase) {
   $assets_manager = \Drupal::service('photoswipe.assets_manager');
 
   // Init.
-  $library_definition = $library_discovery->getLibraryByName('photoswipe', 'photoswipe');
+  $library_definition = $library_discovery->getLibraryByName('photoswipe', 'photoswipe.cdn');
   $min_req_version = $assets_manager->photoswipeMinPluginVersion;
   $max_req_version = $assets_manager->photoswipeMaxPluginVersion;
   $library_req = REQUIREMENT_OK;
@@ -36,8 +39,19 @@ function photoswipe_requirements($phase) {
     '@url' => $library_definition['remote'],
   ]);
 
-  // Library from CDN. Show a warning.
-  if (!$library_file_finder->find('photoswipe')) {
+  $is_locally_installed = $library_file_finder->find('photoswipe');
+
+  if (!$is_cdn_enabled && !$is_locally_installed) {
+    $library_req = REQUIREMENT_ERROR;
+    $message = t('<strong>Photoswipe library not found, and is CDN is disabled!</strong>');
+    $description = t('You can either enable CDN fetching in configuration (here is @link) and library will be fetched automatically or install it locally. Here is instruction:<br>@helptext',
+      [
+        '@helptext' => $help_text,
+        '@link' => Link::createFromRoute(t('link'), 'photoswipe.admin_settings')->toString(),
+      ]
+    );
+  }
+  elseif (!$is_locally_installed) {
     $library_version = $library_definition['version'];
     $library_req = REQUIREMENT_WARNING;
     $message = t('Missing local library. CDN version: @version', ['@version' => $library_version]);
@@ -45,7 +59,7 @@ function photoswipe_requirements($phase) {
     You are using the library from the fallback CDN defined in libraries.yml,
     but local libraries are preferred over CDN. @helptext', ['@helptext' => $help_text]);
   }
-  elseif (!file_exists($library_file_finder->find('photoswipe/photoswipe.json'))) {
+  elseif (!file_exists($library_file_finder->find('photoswipe/package.json'))) {
     $library_req = REQUIREMENT_ERROR;
     $message = t('<strong>Photoswipe library found, but missing photoswipe.json detected!</strong>');
     $description = t('Local library folder found, but library seems to be corrupted, please require the photoswipe library correctly!<br><br>@helptext', ['@helptext' => $help_text]);
@@ -53,7 +67,7 @@ function photoswipe_requirements($phase) {
   // Library detected and local. Check version requirements.
   else {
     // We get the json content as an array here:
-    $photoswipe_json_content = file_get_contents(DRUPAL_ROOT . '/libraries/photoswipe/photoswipe.json');
+    $photoswipe_json_content = file_get_contents(DRUPAL_ROOT . '/libraries/photoswipe/package.json');
     $photoswipe_json = json_decode($photoswipe_json_content, TRUE);
     // If the photswipe.json is not empty get version and check if the
     // installed version does not match version min requirements:
@@ -112,3 +126,23 @@ function photoswipe_update_8314() {
       ->clear('form_id')
       ->save();
 }
+
+/**
+ * Removes obsolete configuration options, and enables caption module.
+ */
+function photoswipe_update_9003() {
+  $config = \Drupal::configFactory()->getEditable('photoswipe.settings');
+
+  $config
+    ->set('enable_cdn', TRUE)
+    ->clear('showHideOpacity')
+    ->clear('closeOnScroll')
+    ->clear('mouseUsed')
+    ->clear('history')
+    ->clear('focus')
+    ->set('maxZoomLevel', $config->get('maxSpreadZoom'))
+    ->clear('maxSpreadZoom')
+    ->save();
+
+  \Drupal::service('module_installer')->install(['photoswipe_caption']);
+}
diff --git a/photoswipe.libraries.yml b/photoswipe.libraries.yml
index b1999224c071a1fafbc8aa642a5c9fc21e079225..f62aa952bc6ed3de9caa9b09492fe21703d4c3d0 100644
--- a/photoswipe.libraries.yml
+++ b/photoswipe.libraries.yml
@@ -1,39 +1,44 @@
-photoswipe:
-  remote: https://github.com/dimsemenov/PhotoSwipe/archive/v4.1.3.zip
-  version: 4.1.3
+photoswipe.cdn:
+  remote: https://github.com/dimsemenov/PhotoSwipe/archive/v5.3.7.zip
+  version: 5.3.7
   license:
     name: MIT
     gpl-compatible: false
   css:
     component:
-      //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css: {}
-      //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css: {}
+      //cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/photoswipe.min.css: { external: true }
   js:
-    //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js: { minified: true }
-    //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js: { minified: true }
+    //cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/umd/photoswipe-lightbox.umd.min.js: { external: true }
+    //cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/umd/photoswipe.umd.min.js: { external: true }
 
-# Development Version
-# Currently unused, see https://www.drupal.org/project/photoswipe/issues/3345238
-photoswipe.dev:
-  remote: https://github.com/dimsemenov/PhotoSwipe/archive/v4.1.3.zip
-  version: 4.1.3
+photoswipe.local:
+  version: VERSION
   license:
     name: MIT
     gpl-compatible: false
   css:
     component:
-      //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css: {}
-      //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css: {}
+      /libraries/photoswipe/dist/photoswipe.css: {}
   js:
-    //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.js: {}
-    //cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.js: {}
+    /libraries/photoswipe/dist/umd/photoswipe.umd.min.js: { minified: true }
+    /libraries/photoswipe/dist/umd/photoswipe-lightbox.umd.min.js: { minified: true }
 
 photoswipe.init:
   js:
-    js/photoswipe.jquery.js: {}
+    js/photoswipe.js: {  }
   dependencies:
-    - core/jquery
+    - photoswipe/prepare_galleries
     - core/drupal
     - core/once
     - core/drupalSettings
-    - photoswipe/photoswipe
+
+# We want to separate preparing galleries logic from photoswipe initialization
+# script, because in the new version we've introduced separate module for the
+# caption plugin. And this ensures that preparation of galleries logic will not be
+# copy pasted, and we will not need to maintain two separate places.
+prepare_galleries:
+  js:
+    js/prepare-galleries.js: {  }
+  dependencies:
+    - core/drupal
+    - core/once
diff --git a/photoswipe.module b/photoswipe.module
index 934ff4b02231bb571a3897f2d02845779e93d26c..d9dbac761c310d9a7b22b2333c6e8b609a1cda8f 100644
--- a/photoswipe.module
+++ b/photoswipe.module
@@ -9,6 +9,8 @@ use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_libraries_info().
+ *
+ * @todo Investigate if this hook is still needed.
  */
 function photoswipe_libraries_info() {
   $libraries['photoswipe'] = [
@@ -16,6 +18,7 @@ function photoswipe_libraries_info() {
     'vendor url' => 'https://github.com/dimsemenov/PhotoSwipe',
     'download url' => 'https://github.com/dimsemenov/PhotoSwipe/archive/master.zip',
     'version arguments' => [
+      // Probably the path should be changed.
       'file' => 'dist/photoswipe.min.js',
       // PhotoSwipe - v4.1.1 - 2015-12-24.
       'pattern' => '/v([\d.]+)/',
@@ -50,9 +53,6 @@ function photoswipe_theme() {
       'template' => 'photoswipe-image-formatter',
       'file' => 'photoswipe.theme.inc',
     ],
-    'photoswipe_container' => [
-      'variables' => [],
-    ],
   ];
 }
 
@@ -86,87 +86,28 @@ function photoswipe_page_attachments(array &$attachments) {
 
 /**
  * Implements hook_library_info_alter().
+ *
+ * If local library is provided take it, otherwise check if CDN is
+ * enabled and if so get library from CDN.
  */
 function photoswipe_library_info_alter(&$libraries, $module) {
-  if ($module == 'photoswipe') {
-    // In case that the libraries are included locally, use those instead of the
-    // CDN.
-    // @see https://www.drupal.org/node/3099614
-    $library_file_finder = \Drupal::service('library.libraries_directory_file_finder');
-    $current_libraries = [
-      'photoswipe' => [
-        'js' => [
-          'photoswipe/dist/photoswipe.min.js',
-          'photoswipe/dist/photoswipe-ui-default.min.js',
-        ],
-        'css' => [
-          'photoswipe/dist/photoswipe.css',
-          'photoswipe/dist/default-skin/default-skin.css',
-        ],
-      ],
-      'photoswipe.dev' => [
-        'js' => [
-          'photoswipe/dist/photoswipe.js',
-          'photoswipe/dist/photoswipe-ui-default.js',
-        ],
-        'css' => [
-          'photoswipe/dist/photoswipe.css',
-          'photoswipe/dist/default-skin/default-skin.css',
-        ],
-      ],
-    ];
-    $is_local = (bool) $library_file_finder->find('photoswipe');
-    if ($is_local) {
-      $photoswipe_json_content = file_get_contents(DRUPAL_ROOT . '/libraries/photoswipe/photoswipe.json');
-      $photoswipe_json = json_decode($photoswipe_json_content, TRUE);
-      // If package.json is empty return and use cdn instead:
-      if (empty($photoswipe_json)) {
-        return;
-      }
-      foreach ($current_libraries as $current_library_id => $current_library_type) {
-        // We also update the version to match the local library.
-        if (isset($libraries[$current_library_id]['version'])) {
-          $version = $photoswipe_json['version'];
-          $libraries[$current_library_id]['version'] = $photoswipe_json_content ? $version : $libraries[$current_library_id]['version'];
-        }
+  if ($module !== 'photoswipe') {
+    return;
+  }
+
+  $library_file_finder = \Drupal::service('library.libraries_directory_file_finder');
+  $is_local = (bool) $library_file_finder->find('photoswipe');
 
-        if (isset($libraries[$current_library_id])) {
-          foreach ($current_library_type as $library_type_id => $current_library_files) {
+  $is_cnd_enabled = \Drupal::config('photoswipe.settings')->get('enable_cdn');
 
-            // @todo it needs to be refactored.
-            if ($library_file_finder->find('photoswipe/dist/photoswipe.min.js')) {
-              if ($library_type_id === 'css') {
-                $libraries[$current_library_id][$library_type_id]['component'] = [];
-              }
-              else {
-                $libraries[$current_library_id][$library_type_id] = [];
-              }
-            }
+  $dependency = match(TRUE) {
+    $is_local => 'photoswipe/photoswipe.local',
+    $is_cnd_enabled => 'photoswipe/photoswipe.cdn',
+    default => NULL,
+  };
 
-            foreach ($current_library_files as $current_library_file) {
-              $path = $library_file_finder->find($current_library_file);
-              if ($path) {
-                if ($library_type_id === 'css') {
-                  $libraries[$current_library_id][$library_type_id]['component'] = array_merge(
-                    [
-                      '/' . $path => [],
-                    ], $libraries[$current_library_id][$library_type_id]['component']);
-                }
-                else {
-                  $libraries[$current_library_id][$library_type_id] = array_merge(
-                    [
-                      '/' . $path =>
-                      ($current_library_id === 'photoswipe.dev')
-                      ? []
-                      : ['minified' => TRUE],
-                    ], $libraries[$current_library_id][$library_type_id]);
-                }
-              }
-            }
-          }
-        }
-      }
-    }
+  if ($dependency !== NULL) {
+    $libraries['photoswipe.init']['dependencies'][] = $dependency;
   }
 }
 
diff --git a/src/Form/PhotoswipeAdminSettings.php b/src/Form/PhotoswipeAdminSettings.php
index 0ef27ea6957ecd3b989d9619b0b4ebdf3365cd97..778fed22164a951c7455940b2c3c9002d7b786f7 100644
--- a/src/Form/PhotoswipeAdminSettings.php
+++ b/src/Form/PhotoswipeAdminSettings.php
@@ -4,7 +4,6 @@ namespace Drupal\photoswipe\Form;
 
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Render\Element;
 
 /**
  * {@inheritdoc}
@@ -18,11 +17,27 @@ class PhotoswipeAdminSettings extends ConfigFormBase {
     return 'photoswipe_admin_settings';
   }
 
-    /**
+  /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
-    return ['photoswipe.settings'];
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->configFactory->get('photoswipe.settings');
+
+    $form['enable_cdn'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Loads PhotoSwipe library from CDN.'),
+      '#default_value' => $config->get('enable_cdn'),
+      '#description' => $this->t('Make sure to check if you can use CDN, because it can be not legally compliant. Check article 28 GDPR'),
+    ];
+
+    $form['photoswipe_always_load_non_admin'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Load PhotoSwipe on all non-admin pages'),
+      '#default_value' => $config->get('photoswipe_always_load_non_admin'),
+      '#description' => $this->t('Useful if you want to use photoswipe elsewhere by just adding the <code>.photoswipe</code> CSS class.'),
+    ];
+
+    return parent::buildForm($form, $form_state);
   }
 
   /**
@@ -30,8 +45,9 @@ class PhotoswipeAdminSettings extends ConfigFormBase {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->config('photoswipe.settings')
-    ->set('photoswipe_always_load_non_admin', $form_state->getValue('photoswipe_always_load_non_admin'))
-    ->save();
+      ->set('photoswipe_always_load_non_admin', $form_state->getValue('photoswipe_always_load_non_admin'))
+      ->set('enable_cdn', $form_state->getValue('enable_cdn'))
+      ->save();
 
     parent::submitForm($form, $form_state);
   }
@@ -39,15 +55,8 @@ class PhotoswipeAdminSettings extends ConfigFormBase {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    $form['photoswipe_always_load_non_admin'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Load PhotoSwipe on all non-admin pages'),
-      '#default_value' => $this->configFactory->get('photoswipe.settings')->get('photoswipe_always_load_non_admin'),
-      '#description' => $this->t('Useful if you want to use photoswipe elsewhere by just adding the <code>.photoswipe</code> CSS class.'),
-    ];
-
-    return parent::buildForm($form, $form_state);
+  protected function getEditableConfigNames() {
+    return ['photoswipe.settings'];
   }
 
 }
diff --git a/src/PhotoswipeAssetsManager.php b/src/PhotoswipeAssetsManager.php
index 6198a59768d81d73af019ff041b6527b3ef453a0..f8f09c63856aedd4cbf1f07512b2f291bab0b6cf 100644
--- a/src/PhotoswipeAssetsManager.php
+++ b/src/PhotoswipeAssetsManager.php
@@ -17,14 +17,14 @@ class PhotoswipeAssetsManager implements PhotoswipeAssetsManagerInterface {
    *
    * @var string
    */
-  public $photoswipeMinPluginVersion = '4.0.0';
+  public $photoswipeMinPluginVersion = '5.2.1';
 
   /**
    * The maximum PhotoSwipe version we support.
    *
    * @var string
    */
-  public $photoswipeMaxPluginVersion = '4.1.3';
+  public $photoswipeMaxPluginVersion = '5.3.7';
 
   /**
    * Whether the assets were attached somewhere in this request or not.
@@ -99,15 +99,13 @@ class PhotoswipeAssetsManager implements PhotoswipeAssetsManagerInterface {
 
     // Add photoswipe js settings.
     $options = $this->config->get('options');
+
     // Allow other modules to alter / extend the options to pass to photoswipe
     // JavaScript.
     $this->moduleHandler->alter('photoswipe_js_options', $options);
     $this->themeManager->alter('photoswipe_js_options', $options);
     $element['#attached']['drupalSettings']['photoswipe']['options'] = $options;
 
-    // Add photoswipe container with class="pswp".
-    $template = ["#theme" => 'photoswipe_container'];
-    $element['#attached']['drupalSettings']['photoswipe']['container'] = $this->renderer->renderPlain($template);
     $this->attached = TRUE;
   }
 
diff --git a/src/PhotoswipePreprocessProcessor.php b/src/PhotoswipePreprocessProcessor.php
index f6a93ec80a752f903e98779a4320fd277591e579..155def282f925d480585d45d77071d10b9294c5d 100644
--- a/src/PhotoswipePreprocessProcessor.php
+++ b/src/PhotoswipePreprocessProcessor.php
@@ -124,7 +124,8 @@ class PhotoswipePreprocessProcessor implements ContainerInjectionInterface {
     $variables['image'] = $image;
     $variables['path'] = $this->getPath();
     $variables['attributes']['class'][] = 'photoswipe';
-    $variables['attributes']['data-size'] = $this->imageDTO->getWidth() . 'x' . $this->imageDTO->getHeight();
+    $variables['attributes']['data-pswp-width'] = $this->imageDTO->getWidth();
+    $variables['attributes']['data-pswp-height'] = $this->imageDTO->getHeight();
     $variables['attributes']['data-overlay-title'] = $this->getCaption();
     if (isset($image['#style_name']) && $image['#style_name'] === 'hide') {
       // Do not display if hidden is selected:
diff --git a/templates/photoswipe-container.html.twig b/templates/photoswipe-container.html.twig
deleted file mode 100644
index 18e4fe2edaca9028689d69275b689b6aa407b2d2..0000000000000000000000000000000000000000
--- a/templates/photoswipe-container.html.twig
+++ /dev/null
@@ -1,78 +0,0 @@
-{# Root element of PhotoSwipe. Must have class pswp. #}
-<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
-
-  {#
-    Background of PhotoSwipe.
-    It's a separate element as animating opacity is faster than rgba().
-  #}
-  <div class="pswp__bg"></div>
-
-  {# Slides wrapper with overflow:hidden. #}
-  <div class="pswp__scroll-wrap">
-
-    {#
-      Container that holds slides.
-      PhotoSwipe keeps only 3 of them in the DOM to save memory.
-      Don't modify these 3 pswp__item elements, data is added later on.
-    #}
-
-    <div class="pswp__container">
-      <div class="pswp__item"></div>
-      <div class="pswp__item"></div>
-      <div class="pswp__item"></div>
-    </div>
-
-    {#
-      Default (PhotoSwipeUI_Default) interface on top of sliding area.
-      Can be changed.
-    #}
-    <div class="pswp__ui pswp__ui--hidden">
-
-      <div class="pswp__top-bar">
-
-        {# Controls are self-explanatory. Order can be changed. #}
-
-        <div class="pswp__counter"></div>
-
-        <button class="pswp__button pswp__button--close" title="{{ 'Close (Esc)'|t }}"></button>
-
-        <button class="pswp__button pswp__button--share" title="{{ 'Share'|t }}"></button>
-
-        <button class="pswp__button pswp__button--fs" title="{{ 'Toggle fullscreen'|t }}"></button>
-
-        <button class="pswp__button pswp__button--zoom" title="{{ 'Zoom in/out'|t }}"></button>
-
-        {#
-          Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR
-          Element will get class pswp__preloader--active when preloader is
-          running.
-        #}
-
-        <div class="pswp__preloader">
-          <div class="pswp__preloader__icn">
-            <div class="pswp__preloader__cut">
-              <div class="pswp__preloader__donut"></div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
-        <div class="pswp__share-tooltip"></div>
-      </div>
-
-      <button class="pswp__button pswp__button--arrow--left" title="{{ 'Previous (arrow left)'|t }}">
-      </button>
-
-      <button class="pswp__button pswp__button--arrow--right" title="{{ 'Next (arrow right)'|t }}">
-      </button>
-
-      <div class="pswp__caption">
-        <div class="pswp__caption__center"></div>
-      </div>
-
-    </div>
-
-  </div>
-
-</div>
diff --git a/tests/modules/photoswipe_cdn_test/photoswipe_cdn_test.info.yml b/tests/modules/photoswipe_cdn_test/photoswipe_cdn_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6f98555ef96a09c91cf6bacc4df03fe588c74d8
--- /dev/null
+++ b/tests/modules/photoswipe_cdn_test/photoswipe_cdn_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Photoswipe cdn test'
+description: 'Adds ability to test CDN.'
+type: module
+package: Testing
+core_version_requirement: ^9.3 || ^10
diff --git a/tests/modules/photoswipe_cdn_test/src/LibrariesDirectoryFileFinder.php b/tests/modules/photoswipe_cdn_test/src/LibrariesDirectoryFileFinder.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d9108221fa2a784ec64eec4e5ecbeb98eb0a90b
--- /dev/null
+++ b/tests/modules/photoswipe_cdn_test/src/LibrariesDirectoryFileFinder.php
@@ -0,0 +1,25 @@
+<?php
+
+// @phpcs:ignoreFile
+
+namespace Drupal\photoswipe_cdn_test;
+
+use Drupal\Core\Asset\LibrariesDirectoryFileFinder as CoreLibrariesDirectoryFileFinder;
+
+/**
+ * Wraps cores finder to provide mock functionality.
+ */
+class LibrariesDirectoryFileFinder extends CoreLibrariesDirectoryFileFinder {
+
+  /**
+   * Adds ability to mock situation when library locally is missing.
+   */
+  public function find($path) {
+    if ($path === 'photoswipe' && \Drupal::config('photoswipe.settings')->get('enable_cdn')) {
+      return FALSE;
+    }
+
+    return parent::find($path);
+  }
+
+}
diff --git a/tests/modules/photoswipe_cdn_test/src/PhotoswipeCdnTestServiceProvider.php b/tests/modules/photoswipe_cdn_test/src/PhotoswipeCdnTestServiceProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c81900ca83327cccd64e034540d3739082f108f
--- /dev/null
+++ b/tests/modules/photoswipe_cdn_test/src/PhotoswipeCdnTestServiceProvider.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\photoswipe_cdn_test;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+
+/**
+ * Alter order receipt subscriber.
+ */
+class PhotoswipeCdnTestServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritDoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    if ($container->hasDefinition('library.libraries_directory_file_finder')) {
+      $definition = $container->getDefinition('library.libraries_directory_file_finder');
+      $definition->setClass(LibrariesDirectoryFileFinder::class);
+    }
+  }
+
+}
diff --git a/tests/src/Functional/GeneralPhotoswipeTest.php b/tests/src/Functional/GeneralPhotoswipeTest.php
index 6cb0d8711a7d66f3413b62c0327d4e2657443e32..6e9f6e804e5aaeb425112c76232f04f7f40358eb 100644
--- a/tests/src/Functional/GeneralPhotoswipeTest.php
+++ b/tests/src/Functional/GeneralPhotoswipeTest.php
@@ -90,7 +90,7 @@ class GeneralPhotoswipeTest extends BrowserTestBase {
     // (The library shouldn't be loaded in the front page):
     $this->drupalGet('<front>');
     $session->statusCodeEquals(200);
-    $session->elementNotExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
+    $session->elementNotExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/5.2.2/photoswipe.min.css"]');
     // Go to the settings page and enable loading on non admin pages:
     $this->drupalGet('/admin/config/media/photoswipe');
     $session->statusCodeEquals(200);
@@ -99,7 +99,7 @@ class GeneralPhotoswipeTest extends BrowserTestBase {
     // Go to the front page again and check if the css file is loaded
     // (The library shouldn't be loaded in the front page):
     $this->drupalGet('<front>');
-    $session->elementNotExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
+    $session->elementNotExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/5.2.2/photoswipe.min.css"]');
     $session->statusCodeEquals(200);
   }
 
@@ -120,7 +120,7 @@ class GeneralPhotoswipeTest extends BrowserTestBase {
     if ($library_path !== FALSE) {
       // Library installed in libraries dir :)
       // So we get library version from photoswipe.json file.
-      $package_json_content = file_get_contents(DRUPAL_ROOT . '/libraries/photoswipe/photoswipe.json');
+      $package_json_content = file_get_contents(DRUPAL_ROOT . '/libraries/photoswipe/package.json');
       $package_json = json_decode($package_json_content, FALSE);
       $installed_version = $package_json->version;
     }
diff --git a/tests/src/FunctionalJavascript/ImageFileTest.php b/tests/src/FunctionalJavascript/ImageFileTest.php
index f93290ddea806bc79a095b369a7d1c2001f401f1..ee474d96d7fe48ac08e91a783d3ada32f93c8390 100644
--- a/tests/src/FunctionalJavascript/ImageFileTest.php
+++ b/tests/src/FunctionalJavascript/ImageFileTest.php
@@ -2,9 +2,6 @@
 
 namespace Drupal\Tests\photoswipe\FunctionalJavascript;
 
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\Tests\TestFileCreationTrait;
 
 /**
@@ -12,7 +9,7 @@ use Drupal\Tests\TestFileCreationTrait;
  *
  * @group photoswipe
  */
-class ImageFileTest extends WebDriverTestBase {
+class ImageFileTest extends PhotoswipeTestBase {
   use TestFileCreationTrait;
 
   /**
@@ -27,112 +24,14 @@ class ImageFileTest extends WebDriverTestBase {
     'node',
     'field_ui',
     'photoswipe',
+    'photoswipe_cdn_test',
   ];
 
-  /**
-   * A user with admin permissions.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $adminUser;
-
-  /**
-   * A user with authenticated permissions.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $user;
-
   /**
    * {@inheritdoc}
    */
   protected $defaultTheme = 'stark';
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->publicFilesDirectory = 'public://';
-    $this->config('system.site')->set('page.front', '/test-page')->save();
-
-    $this->user = $this->drupalCreateUser([]);
-    $this->adminUser = $this->drupalCreateUser([]);
-    $this->adminUser->addRole($this->createAdminRole('admin', 'admin'));
-    $this->adminUser->save();
-    $this->drupalLogin($this->adminUser);
-
-    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
-  }
-
-  /**
-   * Create a new image field.
-   *
-   * Create a new image field.
-   * Modified Version of Drupal\Tests\image\Kernel\ImageFieldCreationTrait
-   * "createImageField" function.
-   *
-   * @param string $name
-   *   The name of the new field (all lowercase), exclude the "field_" prefix.
-   * @param string $entity_type_id
-   *   The entity type that this field will be added to.
-   * @param string $bundle_id
-   *   The entity type bundle that this field will be added to.
-   * @param array $storage_settings
-   *   (optional) A list of field storage settings that will be added to the
-   *   defaults.
-   * @param array $field_settings
-   *   (optional) A list of instance settings that will be added to the instance
-   *   defaults.
-   * @param array $widget_settings
-   *   (optional) Widget settings to be added to the widget defaults.
-   * @param array $formatter_settings
-   *   (optional) Formatter settings to be added to the formatter defaults.
-   * @param string $formatter_type
-   *   (optional) The formatter type, defaults to 'photoswipe_field_formatter'.
-   * @param string $description
-   *   (optional) A description for the field. Defaults to ''.
-   */
-  protected function createImageField($name, $entity_type_id = 'node', $bundle_id = 'article', array $storage_settings = [], array $field_settings = [], array $widget_settings = [], array $formatter_settings = [], $formatter_type = 'photoswipe_field_formatter', $description = '') {
-    FieldStorageConfig::create([
-      'field_name' => $name,
-      'entity_type' => $entity_type_id,
-      'type' => 'image',
-      'settings' => $storage_settings,
-      'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
-    ])->save();
-
-    $field_config = FieldConfig::create([
-      'field_name' => $name,
-      'label' => $name,
-      'entity_type' => $entity_type_id,
-      'bundle' => $bundle_id,
-      'required' => !empty($field_settings['required']),
-      'settings' => $field_settings,
-      'description' => $description,
-    ]);
-    $field_config->save();
-
-    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
-    $display_repository = \Drupal::service('entity_display.repository');
-    $display_repository->getFormDisplay($entity_type_id, $bundle_id)
-      ->setComponent($name, [
-        'type' => 'image_image',
-        'settings' => $widget_settings,
-      ])
-      ->save();
-
-    $display_repository->getViewDisplay($entity_type_id, $bundle_id)
-      ->setComponent($name, [
-        'type' => $formatter_type,
-        'settings' => $formatter_settings,
-      ])
-      ->save();
-
-    return $field_config;
-  }
-
   // /**
   //  * Tests if the Photoswipe field formatter settings exist.
   //  */
@@ -169,6 +68,64 @@ class ImageFileTest extends WebDriverTestBase {
   //   $session->pageTextContains('Your settings have been saved.');
   // }
 
+  /**
+   * Tests situation when local library is missing and cdn is enabled.
+   */
+  public function testCdnLibrary() {
+    // We install library locally during drupal_ci build. Because of this
+    // photoswipe_cdn_test module provides ability to mock situation when CDN
+    // is enabled.
+    // @see \Drupal\photoswipe_cdn_test\LibrariesDirectoryFileFinder::find
+    \Drupal::configFactory()->getEditable('photoswipe.settings')->set('enable_cdn', TRUE)->save();
+    $session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $field_settings = ['alt_field_required' => 1];
+    $this->createImageField('field_test', 'node', 'article', [
+      'uri_scheme' => 'public',
+      'required' => 'true',
+    ], $field_settings);
+    // Create the node with a test file uploaded:
+    $this->drupalGet('node/add/article');
+    $title = 'My test content';
+    $page->fillField('title[0][value]', $title);
+    $this->assertNotEmpty($image_upload_field = $page->find('css', '#edit-field-test-0-upload'));
+    $image = $this->getTestFiles('image')[0];
+    $image_upload_field->attachFile($this->container->get('file_system')->realpath($image->uri));
+    $session->waitForElementVisible('css', '.image-preview');
+    $session->pageTextContains('Alternative text');
+    $page->fillField('Alternative text', 'Alt text');
+    $page->pressButton('edit-submit');
+    $session->pageTextContains("Article {$title} has been created.");
+    $this->drupalGet('/node/1');
+    $this->validateCdnLibraries($session);
+    // Check if the fallback wrapper is loaded with the correct
+    // classes and attributes:
+    // Wait for the JavaScript to initialize the fallback wrapper:
+    $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
+    $session->elementExists('css', '.photoswipe-gallery');
+    $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
+    // Check if the anker element is set with the correct classes, wrappers and
+    // attributes:
+    $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
+    $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
+    $session->elementAttributeContains('css', 'a[href*="image-test.png"].photoswipe', 'data-overlay-title', 'Alt text');
+    // Check if the image is loaded with the correct defaults and wrappers:
+    $session->elementExists('css', 'img[src*="image-test.png"]');
+    $session->elementExists('css', 'a[href*="image-test.png"].photoswipe > img[src*="image-test.png"]');
+    $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe > img[src*="image-test.png"]');
+    // Uploaded pictures are not broken during testing, but only on later
+    // inspection. See https://www.drupal.org/project/drupal/issues/3272192.
+    $session->elementAttributeContains('css', 'img[src*="image-test.png"]', 'width', '40');
+    $session->elementAttributeContains('css', 'img[src*="image-test.png"]', 'height', '20');
+    $this->getSession()->getPage()->find('css', 'a[href*="image-test.png"].photoswipe')->click();
+    $session->waitForElementVisible('css', '.pswp');
+
+    // Disable cdn.
+    \Drupal::configFactory()->getEditable('photoswipe.settings')->set('enable_cdn', FALSE)->save();
+  }
+
   /**
    * Tests the photoswipe formatter on node display.
    */
@@ -195,23 +152,17 @@ class ImageFileTest extends WebDriverTestBase {
     $session->pageTextContains("Article {$title} has been created.");
     $this->drupalGet('/node/1');
     // $this->getSession()->wait(5000, "document.readyState === 'complete'");
-    // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // Check if the fallback wrapper is loaded with the correct
     // classes and attributes:
     // Wait for the JavaScript to initialize the fallback wrapper:
     $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
     $session->elementExists('css', '.photoswipe-gallery');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     $session->elementAttributeContains('css', 'a[href*="image-test.png"].photoswipe', 'data-overlay-title', 'Alt text');
     // Check if the image is loaded with the correct defaults and wrappers:
     $session->elementExists('css', 'img[src*="image-test.png"]');
@@ -317,23 +268,17 @@ class ImageFileTest extends WebDriverTestBase {
     $page->pressButton('edit-submit');
     $session->pageTextContains("Article {$title} has been created.");
     $this->drupalGet('/node/1');
-    // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // Check if the fallback wrapper is loaded with the correct
     // classes and attributes:
     // Wait for the JavaScript to initialize the fallback wrapper:
     $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
     $session->elementExists('css', '.photoswipe-gallery');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     // Check if the image is loaded with the correct defaults and wrappers:
     $session->elementExists('css', 'img[src*="image-test.png"]');
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe > img[src*="image-test.png"]');
@@ -344,7 +289,7 @@ class ImageFileTest extends WebDriverTestBase {
     // attributes for the second picture:
     $session->elementExists('css', 'a[href*="image-test_0.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test_0.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test_0.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test_0.png"].photoswipe', 'data-pswp-width');
     // Check if the image is loaded with the correct defaults and wrappers for
     // the second picture:
     $session->elementExists('css', 'img[src*="image-test_0.png"]');
@@ -354,7 +299,6 @@ class ImageFileTest extends WebDriverTestBase {
     // inspection. See https://www.drupal.org/project/drupal/issues/3272192.
     $session->elementAttributeContains('css', 'img[src*="image-test_0.png"]', 'width', '40');
     $session->elementAttributeContains('css', 'img[src*="image-test_0.png"]', 'height', '20');
-    // @todo Check the photoswipe functionalities here.
   }
 
   /**
@@ -386,11 +330,6 @@ class ImageFileTest extends WebDriverTestBase {
     $page->pressButton('edit-submit');
     $session->pageTextContains("Article {$title} has been created.");
     $this->drupalGet('/node/1');
-    // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // Check if the fallback wrapper is loaded with the correct
     // classes and attributes:
     $session->elementExists('css', '.photoswipe-gallery');
@@ -398,13 +337,12 @@ class ImageFileTest extends WebDriverTestBase {
     $session->elementNotExists('css', 'div.photoswipe-gallery--fallback-wrapper');
     // Check, that there is no fallback wrapper span:
     $session->elementNotExists('css', '.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', 'div.photoswipe-gallery', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes for the first picture:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery div > a[href*="image-test.png"].photoswipe');
 
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     // Check if the image is loaded with the correct defaults and wrappers for
     // the first picture:
     $session->elementExists('css', 'img[src*="image-test.png"]');
@@ -416,7 +354,7 @@ class ImageFileTest extends WebDriverTestBase {
     // attributes for the second picture:
     $session->elementExists('css', 'a[href*="image-test_0.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery div> a[href*="image-test_0.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test_0.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test_0.png"].photoswipe', 'data-pswp-width');
     // Check if the image is loaded with the correct defaults and wrappers for
     // the second picture:
     $session->elementExists('css', 'img[src*="image-test_0.png"]');
diff --git a/tests/src/FunctionalJavascript/MediaReferenceTest.php b/tests/src/FunctionalJavascript/MediaReferenceTest.php
index f97d727c0670d615c00bfdae940e87bee61207fc..4e88b5e4d30531d0a9de5283fca1754b920e77de 100644
--- a/tests/src/FunctionalJavascript/MediaReferenceTest.php
+++ b/tests/src/FunctionalJavascript/MediaReferenceTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\photoswipe\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
 use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
 use Drupal\Tests\TestFileCreationTrait;
@@ -12,7 +11,7 @@ use Drupal\Tests\TestFileCreationTrait;
  *
  * @group photoswipe
  */
-class MediaReferenceTest extends WebDriverTestBase {
+class MediaReferenceTest extends PhotoswipeTestBase {
   use TestFileCreationTrait, EntityReferenceTestTrait, MediaTypeCreationTrait;
 
   /**
@@ -30,43 +29,11 @@ class MediaReferenceTest extends WebDriverTestBase {
     'photoswipe',
   ];
 
-  /**
-   * A user with admin permissions.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $adminUser;
-
-  /**
-   * A user with authenticated permissions.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $user;
-
   /**
    * {@inheritdoc}
    */
   protected $defaultTheme = 'stark';
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->publicFilesDirectory = 'public://';
-    $this->config('system.site')->set('page.front', '/test-page')->save();
-
-    $this->user = $this->drupalCreateUser([]);
-    $this->adminUser = $this->drupalCreateUser([]);
-    $this->adminUser->addRole($this->createAdminRole('admin', 'admin'));
-    $this->adminUser->save();
-    $this->drupalLogin($this->adminUser);
-
-    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
-  }
-
   /**
    * Helper function to create a media image.
    */
@@ -170,11 +137,6 @@ class MediaReferenceTest extends WebDriverTestBase {
     $session->pageTextContains("Article {$title} has been created.");
     $this->getSession()->wait(5000, 'typeof window.jQuery == "function"');
     $this->drupalGet('/node/1');
-    // // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // // Check if the fallback wrapper is loaded with the correct
     // // classes and attributes:
     // Wait for the JavaScript to initialize the fallback wrapper:
@@ -182,12 +144,11 @@ class MediaReferenceTest extends WebDriverTestBase {
     $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
     $session->elementExists('css', '.photoswipe-gallery');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     $session->elementAttributeContains('css', 'a[href*="image-test.png"].photoswipe', 'data-overlay-title', 'Alt text');
     // Check if the image is loaded with the correct defaults and wrappers:
     $session->elementExists('css', 'img[src*="image-test.png"]');
@@ -408,23 +369,17 @@ class MediaReferenceTest extends WebDriverTestBase {
     $page->pressButton('edit-submit');
     $session->pageTextContains("Article {$title} has been created.");
     $this->drupalGet('/node/1');
-    // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // Check if the fallback wrapper is loaded with the correct
     // classes and attributes:
     // Wait for the JavaScript to initialize the fallback wrapper:
     $session->waitForElement('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
     $session->elementExists('css', '.photoswipe-gallery');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', '.photoswipe-gallery.photoswipe-gallery--fallback-wrapper > a[href*="image-test.png"].photoswipe');
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     $session->elementAttributeContains('css', 'a[href*="image-test.png"].photoswipe', 'data-overlay-title', 'Alt text');
     // Check if the image is loaded with the correct defaults and wrappers:
     $session->elementExists('css', 'img[src*="image-test.png"]');
@@ -476,11 +431,6 @@ class MediaReferenceTest extends WebDriverTestBase {
     $page->fillField('media_image (value 2)', 'image-test.png');
     $page->pressButton('edit-submit');
     $this->drupalGet('/node/1');
-    // Check if all necessary js and css files are loaded:
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.css"]');
-    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.css"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"]');
-    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"]');
     // Check if the fallback wrapper is loaded with the correct
     // classes and attributes:
     $session->elementExists('css', 'div.photoswipe-gallery');
@@ -488,13 +438,12 @@ class MediaReferenceTest extends WebDriverTestBase {
     $session->elementNotExists('css', 'div.photoswipe-gallery--fallback-wrapper');
     // Check, that there is no fallback wrapper span:
     $session->elementNotExists('css', '.photoswipe-gallery--fallback-wrapper');
-    $session->elementAttributeExists('css', 'div.photoswipe-gallery', 'data-pswp-uid');
     // Check if the anker element is set with the correct classes, wrappers and
     // attributes for the first picture:
     $session->elementExists('css', 'a[href*="image-test.png"].photoswipe');
     $session->elementExists('css', 'div.photoswipe-gallery div > a[href*="image-test.png"].photoswipe');
 
-    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-size');
+    $session->elementAttributeExists('css', 'a[href*="image-test.png"].photoswipe', 'data-pswp-width');
     // Check if the image is loaded with the correct defaults and wrappers for
     // the first picture:
     $session->elementExists('css', 'img[src*="image-test.png"]');
diff --git a/tests/src/FunctionalJavascript/PhotoswipeTestBase.php b/tests/src/FunctionalJavascript/PhotoswipeTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..c3aebab48029f832060be52bf4cf30b72a0f0c64
--- /dev/null
+++ b/tests/src/FunctionalJavascript/PhotoswipeTestBase.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Drupal\Tests\photoswipe\FunctionalJavascript;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+
+/**
+ * Tests the photoswipe display setting on an image file.
+ *
+ * @group photoswipe
+ */
+abstract class PhotoswipeTestBase extends WebDriverTestBase {
+
+  /**
+   * A user with admin permissions.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $adminUser;
+
+  /**
+   * A user with authenticated permissions.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $user;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->publicFilesDirectory = 'public://';
+    $this->config('system.site')->set('page.front', '/test-page')->save();
+
+    $this->user = $this->drupalCreateUser([]);
+    $this->adminUser = $this->drupalCreateUser([]);
+    $this->adminUser->addRole($this->createAdminRole('admin', 'admin'));
+    $this->adminUser->save();
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+  }
+
+  /**
+   * Validates if cdn libraries are loaded properly.
+   */
+  protected function validateCdnLibraries($session): void {
+    $session->elementExists('css', 'link[href*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/photoswipe.min.css"]');
+    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/umd/photoswipe-lightbox.umd.min.js"]');
+    $session->elementExists('css', 'script[src*="//cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/umd/photoswipe.umd.min.js"]');
+  }
+
+  /**
+   * Create a new image field.
+   *
+   * Create a new image field.
+   * Modified Version of Drupal\Tests\image\Kernel\ImageFieldCreationTrait
+   * "createImageField" function.
+   *
+   * @param string $name
+   *   The name of the new field (all lowercase), exclude the "field_" prefix.
+   * @param string $entity_type_id
+   *   The entity type that this field will be added to.
+   * @param string $bundle_id
+   *   The entity type bundle that this field will be added to.
+   * @param array $storage_settings
+   *   (optional) A list of field storage settings that will be added to the
+   *   defaults.
+   * @param array $field_settings
+   *   (optional) A list of instance settings that will be added to the instance
+   *   defaults.
+   * @param array $widget_settings
+   *   (optional) Widget settings to be added to the widget defaults.
+   * @param array $formatter_settings
+   *   (optional) Formatter settings to be added to the formatter defaults.
+   * @param string $formatter_type
+   *   (optional) The formatter type, defaults to 'photoswipe_field_formatter'.
+   * @param string $description
+   *   (optional) A description for the field. Defaults to ''.
+   */
+  protected function createImageField($name, $entity_type_id = 'node', $bundle_id = 'article', array $storage_settings = [], array $field_settings = [], array $widget_settings = [], array $formatter_settings = [], $formatter_type = 'photoswipe_field_formatter', $description = '') {
+    FieldStorageConfig::create([
+      'field_name' => $name,
+      'entity_type' => $entity_type_id,
+      'type' => 'image',
+      'settings' => $storage_settings,
+      'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
+    ])->save();
+
+    $field_config = FieldConfig::create([
+      'field_name' => $name,
+      'label' => $name,
+      'entity_type' => $entity_type_id,
+      'bundle' => $bundle_id,
+      'required' => !empty($field_settings['required']),
+      'settings' => $field_settings,
+      'description' => $description,
+    ]);
+    $field_config->save();
+
+    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
+    $display_repository = \Drupal::service('entity_display.repository');
+    $display_repository->getFormDisplay($entity_type_id, $bundle_id)
+      ->setComponent($name, [
+        'type' => 'image_image',
+        'settings' => $widget_settings,
+      ])
+      ->save();
+
+    $display_repository->getViewDisplay($entity_type_id, $bundle_id)
+      ->setComponent($name, [
+        'type' => $formatter_type,
+        'settings' => $formatter_settings,
+      ])
+      ->save();
+
+    return $field_config;
+  }
+
+}