diff --git a/typo3/sysext/core/Classes/Pagination/SlidingWindowPagination.php b/typo3/sysext/core/Classes/Pagination/SlidingWindowPagination.php
new file mode 100644
index 0000000000000000000000000000000000000000..b017fce90ef8020b6aadb5eb414b1c3f5f32e64c
--- /dev/null
+++ b/typo3/sysext/core/Classes/Pagination/SlidingWindowPagination.php
@@ -0,0 +1,149 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Pagination;
+
+final class SlidingWindowPagination implements PaginationInterface
+{
+    protected int $displayRangeStart = 0;
+    protected int $displayRangeEnd = 0;
+    protected bool $hasLessPages = false;
+    protected bool $hasMorePages = false;
+    protected int $maximumNumberOfLinks = 0;
+    protected PaginatorInterface $paginator;
+
+    public function __construct(PaginatorInterface $paginator, int $maximumNumberOfLinks = 0)
+    {
+        $this->paginator = $paginator;
+
+        if ($maximumNumberOfLinks > 0) {
+            $this->maximumNumberOfLinks = $maximumNumberOfLinks;
+        }
+
+        $this->calculateDisplayRange();
+    }
+
+    public function getPreviousPageNumber(): ?int
+    {
+        $previousPage = $this->paginator->getCurrentPageNumber() - 1;
+
+        if ($previousPage > $this->paginator->getNumberOfPages()) {
+            return null;
+        }
+
+        return $previousPage >= $this->getFirstPageNumber() ? $previousPage : null;
+    }
+
+    public function getNextPageNumber(): ?int
+    {
+        $nextPage = $this->paginator->getCurrentPageNumber() + 1;
+
+        return $nextPage <= $this->paginator->getNumberOfPages() ? $nextPage : null;
+    }
+
+    public function getFirstPageNumber(): int
+    {
+        return 1;
+    }
+
+    public function getLastPageNumber(): int
+    {
+        return $this->paginator->getNumberOfPages();
+    }
+
+    public function getStartRecordNumber(): int
+    {
+        if ($this->paginator->getCurrentPageNumber() > $this->paginator->getNumberOfPages()) {
+            return 0;
+        }
+
+        return $this->paginator->getKeyOfFirstPaginatedItem() + 1;
+    }
+
+    public function getEndRecordNumber(): int
+    {
+        if ($this->paginator->getCurrentPageNumber() > $this->paginator->getNumberOfPages()) {
+            return 0;
+        }
+
+        return $this->paginator->getKeyOfLastPaginatedItem() + 1;
+    }
+
+    public function getAllPageNumbers(): array
+    {
+        return range($this->displayRangeStart, $this->displayRangeEnd);
+    }
+
+    public function getDisplayRangeStart(): int
+    {
+        return $this->displayRangeStart;
+    }
+
+    public function getDisplayRangeEnd(): int
+    {
+        return $this->displayRangeEnd;
+    }
+
+    public function getHasLessPages(): bool
+    {
+        return $this->hasLessPages;
+    }
+
+    public function getHasMorePages(): bool
+    {
+        return $this->hasMorePages;
+    }
+
+    public function getMaximumNumberOfLinks(): int
+    {
+        return $this->maximumNumberOfLinks;
+    }
+
+    public function getPaginator(): PaginatorInterface
+    {
+        return $this->paginator;
+    }
+
+    protected function calculateDisplayRange(): void
+    {
+        $maximumNumberOfLinks = $this->maximumNumberOfLinks;
+        $numberOfPages = $this->paginator->getNumberOfPages();
+
+        if ($maximumNumberOfLinks > $numberOfPages) {
+            $maximumNumberOfLinks = $numberOfPages;
+        }
+
+        $currentPage = $this->paginator->getCurrentPageNumber();
+        $delta = floor($maximumNumberOfLinks / 2);
+
+        $this->displayRangeStart = (int)($currentPage - $delta);
+        $this->displayRangeEnd = (int)($currentPage + $delta - ($maximumNumberOfLinks % 2 === 0 ? 1 : 0));
+
+        if ($this->displayRangeStart < 1) {
+            $this->displayRangeEnd -= $this->displayRangeStart - 1;
+        }
+
+        if ($this->displayRangeEnd > $numberOfPages) {
+            $this->displayRangeStart -= $this->displayRangeEnd - $numberOfPages;
+        }
+
+        $this->displayRangeStart = (int)max($this->displayRangeStart, 1);
+        $this->displayRangeEnd = (int)min($this->displayRangeEnd, $numberOfPages);
+        $this->hasLessPages = $this->displayRangeStart > 2;
+        $this->hasMorePages = $this->displayRangeEnd + 1 < $this->paginator->getNumberOfPages();
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Feature-94625-IntroduceSlidingWindowPagination.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-94625-IntroduceSlidingWindowPagination.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3de5e9f008641dbc616405edc580a3e7d3bae881
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-94625-IntroduceSlidingWindowPagination.rst
@@ -0,0 +1,65 @@
+.. include:: ../../Includes.txt
+
+=====================================================
+Feature: #94625 - Introduce sliding window pagination
+=====================================================
+
+See :issue:`94625`
+
+Description
+===========
+
+Since TYPO3 10 a new `Pagination API <https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Pagination/Index.html>`__
+is shipped, which supersedes the pagination widget controller, which had
+been removed in TYPO3 11.
+
+This patch provides an improved pagination which can be used to paginate array
+items or query results from Extbase. The main advantage is that it reduces the
+amount of pages shown.
+
+**Example**: Imagine 1000 records and 20 items per page which would lead to
+50 links. Using the `SlidingWindowPagination`, you will get something like
+`< 1 2 ... 21 22 23 24 ... 100 >`.
+
+Usage
+=====
+
+Just replace the usage of :php:`SimplePagination` with
+:php:`\TYPO3\CMS\Core\Pagination\SlidingWindowPagination` and you are done.
+Set the 2nd argument to the maximum number of links which should be rendered.
+
+.. code-block:: php
+
+   $currentPage = $this->request->hasArgument('currentPage')
+      ? (int)$this->request->getArgument('currentPage')
+      : 1;
+   $itemsPerPage = 10;
+   $maximumLinks = 15;
+
+   $paginator = new \TYPO3\CMS\Extbase\Pagination\QueryResultPaginator(
+      $allItems,
+      $currentPage,
+      $itemsPerPage
+   );
+   $pagination = new \TYPO3\CMS\Core\Pagination\SlidingWindowPagination(
+      $paginator,
+      $maximumLinks
+   );
+
+   $this->view->assign(
+      'pagination',
+      [
+         'pagination' => $pagination,
+         'paginator' => $paginator
+      ]
+   );
+
+Credits
+=======
+
+This patch is loosely based on the "`numbered_pagination <https://github.com/georgringer/numbered_pagination>`__"
+extension by Georg Ringer.
+
+Thanks to him.
+
+.. index:: PHP-API, ext:core
diff --git a/typo3/sysext/core/Tests/Unit/Pagination/SlidingWindowPaginationTest.php b/typo3/sysext/core/Tests/Unit/Pagination/SlidingWindowPaginationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..535467a1571918b3237eb2576e5fe3608f6f86ff
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Pagination/SlidingWindowPaginationTest.php
@@ -0,0 +1,148 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Unit\Pagination;
+
+use TYPO3\CMS\Core\Pagination\ArrayPaginator;
+use TYPO3\CMS\Core\Pagination\SlidingWindowPagination;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class SlidingWindowPaginationTest extends UnitTestCase
+{
+    protected $paginator = [];
+
+    /**
+     * @test
+     */
+    public function checkSlidingWindowPaginationWithAPaginatorWithDefaultSettings(): void
+    {
+        $pagination = new SlidingWindowPagination($this->paginator, 5);
+
+        self::assertSame(1, $pagination->getStartRecordNumber());
+        self::assertSame(10, $pagination->getEndRecordNumber());
+        self::assertSame(1, $pagination->getFirstPageNumber());
+        self::assertSame(2, $pagination->getLastPageNumber());
+        self::assertNull($pagination->getPreviousPageNumber());
+        self::assertSame(2, $pagination->getNextPageNumber());
+        self::assertSame([1, 2], $pagination->getAllPageNumbers());
+        self::assertSame(1, $pagination->getDisplayRangeStart());
+        self::assertSame(2, $pagination->getDisplayRangeEnd());
+        self::assertFalse($pagination->getHasLessPages());
+        self::assertFalse($pagination->getHasMorePages());
+        self::assertSame(5, $pagination->getMaximumNumberOfLinks());
+    }
+
+    /**
+     * @test
+     */
+    public function checkSlidingWindowPaginationWithAnIncreasedCurrentPageNumber(): void
+    {
+        $paginator = $this->paginator->withCurrentPageNumber(2);
+        $pagination = new SlidingWindowPagination($paginator, 5);
+
+        self::assertSame(11, $pagination->getStartRecordNumber());
+        self::assertSame(14, $pagination->getEndRecordNumber());
+        self::assertSame(1, $pagination->getFirstPageNumber());
+        self::assertSame(2, $pagination->getLastPageNumber());
+        self::assertSame(1, $pagination->getPreviousPageNumber());
+        self::assertNull($pagination->getNextPageNumber());
+        self::assertSame([1, 2], $pagination->getAllPageNumbers());
+        self::assertSame(1, $pagination->getDisplayRangeStart());
+        self::assertSame(2, $pagination->getDisplayRangeEnd());
+        self::assertFalse($pagination->getHasLessPages());
+        self::assertFalse($pagination->getHasMorePages());
+        self::assertSame(5, $pagination->getMaximumNumberOfLinks());
+    }
+
+    /**
+     * @test
+     */
+    public function checkSlidingWindowPaginationWithAnIncreasedCurrentPageNumberAndItemsPerPage(): void
+    {
+        $paginator = $this->paginator
+            ->withCurrentPageNumber(2)
+            ->withItemsPerPage(3);
+        $pagination = new SlidingWindowPagination($paginator, 5);
+
+        self::assertSame(4, $pagination->getStartRecordNumber());
+        self::assertSame(6, $pagination->getEndRecordNumber());
+        self::assertSame(1, $pagination->getFirstPageNumber());
+        self::assertSame(5, $pagination->getLastPageNumber());
+        self::assertSame(1, $pagination->getPreviousPageNumber());
+        self::assertSame(3, $pagination->getNextPageNumber());
+        self::assertSame([1, 2, 3, 4, 5], $pagination->getAllPageNumbers());
+        self::assertSame(1, $pagination->getDisplayRangeStart());
+        self::assertSame(5, $pagination->getDisplayRangeEnd());
+        self::assertFalse($pagination->getHasLessPages());
+        self::assertFalse($pagination->getHasMorePages());
+        self::assertSame(5, $pagination->getMaximumNumberOfLinks());
+    }
+
+    /**
+     * @test
+     */
+    public function checkPaginationWithAPaginatorThatOnlyHasOnePage(): void
+    {
+        $paginator = $this->paginator->withItemsPerPage(50);
+        $pagination = new SlidingWindowPagination($paginator, 5);
+
+        self::assertSame(1, $pagination->getStartRecordNumber());
+        self::assertSame(14, $pagination->getEndRecordNumber());
+        self::assertSame(1, $pagination->getFirstPageNumber());
+        self::assertSame(1, $pagination->getLastPageNumber());
+        self::assertNull($pagination->getPreviousPageNumber());
+        self::assertNull($pagination->getNextPageNumber());
+        self::assertSame([1], $pagination->getAllPageNumbers());
+        self::assertSame(1, $pagination->getDisplayRangeStart());
+        self::assertSame(1, $pagination->getDisplayRangeEnd());
+        self::assertFalse($pagination->getHasLessPages());
+        self::assertFalse($pagination->getHasMorePages());
+        self::assertSame(5, $pagination->getMaximumNumberOfLinks());
+    }
+
+    /**
+     * @test
+     */
+    public function checkPaginatorWithOutOfBoundsCurrentPage(): void
+    {
+        $paginator = $this->paginator
+            ->withItemsPerPage(5)
+            ->withCurrentPageNumber(100);
+        $pagination = new SlidingWindowPagination($paginator, 5);
+
+        self::assertSame(11, $pagination->getStartRecordNumber());
+        self::assertSame(14, $pagination->getEndRecordNumber());
+        self::assertSame(3, $paginator->getCurrentPageNumber());
+        self::assertSame(1, $pagination->getFirstPageNumber());
+        self::assertSame(2, $pagination->getPreviousPageNumber());
+        self::assertNull($pagination->getNextPageNumber());
+        self::assertSame(3, $pagination->getLastPageNumber());
+        self::assertSame([1, 2, 3], $pagination->getAllPageNumbers());
+        self::assertSame(1, $pagination->getDisplayRangeStart());
+        self::assertSame(3, $pagination->getDisplayRangeEnd());
+        self::assertFalse($pagination->getHasLessPages());
+        self::assertFalse($pagination->getHasMorePages());
+        self::assertSame(5, $pagination->getMaximumNumberOfLinks());
+    }
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        $this->paginator = new ArrayPaginator(range(1, 14));
+    }
+}