diff --git a/Build/Sources/Sass/dashboard/_grid.scss b/Build/Sources/Sass/dashboard/_grid.scss
index bb6eb4a0b425e6daf9aadd1bf3cb42110749c136..e79c19e7146e5091d60aa6a32d9c7b23fb362f62 100644
--- a/Build/Sources/Sass/dashboard/_grid.scss
+++ b/Build/Sources/Sass/dashboard/_grid.scss
@@ -72,19 +72,19 @@
     user-select: auto !important;
 }
 
-.dashboard-item--h4 {
+.dashboard-item--h-medium {
     @media screen and (min-width: 750px) {
         height: $widget-height * 2;
     }
 }
 
-.dashboard-item--h6 {
+.dashboard-item--h-large {
     @media screen and (min-width: 750px) {
         height: $widget-height * 3;
     }
 }
 
-.dashboard-item--w4 {
+.dashboard-item--w-medium {
     width: 100%;
 
     @media screen and (min-width: 1285px) {
@@ -92,6 +92,10 @@
     }
 }
 
+.dashboard-item--w-large {
+    width: 100%;
+}
+
 .dashboard-item-content {
     position: relative;
     width: 100%;
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7c32bc0f88682af6d36110075a62a0289c3c46a0
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-90660-RegistrationOfWidgetsChanged.rst
@@ -0,0 +1,138 @@
+.. include:: ../../Includes.txt
+
+============================================================
+Breaking: #90660 - Registration of dashboard widgets changed
+============================================================
+
+See :issue:`90660`
+
+Description
+===========
+
+As the registration of dashboard widgets changed to allow creation of widgets
+through configuration, it is necessary to change your registration of widgets you
+registered yourself in version 10.3. The abstracts used to kick start your
+widgets are removed and the widgets shipped with EXT:dashboard are refactored.
+
+Impact
+======
+As the abstracts previously used to kick-start a widget are removed, you need
+to change to the new way of registering widgets. It will break the dashboard
+module if you do not update your registration.
+
+Affected Installations
+======================
+
+All 3rd party extensions that registered an own widget with TYPO3 v10.3, will be
+affected and need to update the widget registration. If you only used the widgets
+shipped with core, you don't have to do anything yourself.
+
+Migration
+=========
+
+Migration of widgets based on default widget types
+--------------------------------------------------
+
+This section is showing you how to migrate widgets that are based on one of
+the existing widget types that are shipped by core. If your widgets are extending
+one of the following classes, you can use this section to migrate your registration
+to the new syntax.
+
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractChartWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractCtaButtonWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractListWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractNumberWithIconWidget`
+- :php:`\TYPO3\CMS\Dashboard\Widgets\AbstractRssWidget`
+
+First of all you need to update your registration in the :file:`Services.yaml` file.
+An example of a registration of an RSS widget in the old version.
+
+**Before**
+
+.. code-block:: yaml
+
+ Vendor\Package\Widgets\MyOwnRSSWidget:
+   arguments: [‘myOwnRSSWidget’]
+   tags:
+     - name: dashboard.widget
+       identifier: myOwnRSSWidget
+       widgetGroups: ‘general’
+
+As you can now use the predefined widgets and only have to register your own
+implementation with your own configuration, we have to alter this registration
+a little bit.
+
+**Now**
+
+.. code-block:: yaml
+
+   dashboard.widget.myOwnRSSWidget:
+     class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
+     arguments:
+       $view: '@dashboard.views.widget'
+       $cache: '@cache.dashboard.rss'
+       $options:
+         rssFile: 'https://typo3.org/rss'
+         # 12 hours cache
+         lifeTime: 43200
+     tags:
+       - name: dashboard.widget
+         identifier: 'myOwnRSSWidget'
+         groupNames: ‘general’
+         title: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.title'
+         description: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.description'
+         iconIdentifier: 'content-widget-rss'
+         height: 4
+         width: 4
+
+It starts with the name of the service. Best practise is to use a dot-styled
+name as there will be now class with that name. On the second line, we define
+which widget to use. In this case we choose the RssWidget from the dashboard
+core extension. In the documentation, we explain all the arguments like :php:`$view`
+and :php:`$cache`. For the migration you need the :php:`$options` argument.
+As you can see we specify the RSS File and the cache lifetime for this feed.
+In the old situation you have set these values in the class.
+Now you can just put those values in the registration.
+
+The second part that changed a little bit, is that you need to set the title,
+description, icon, height and width in the tags section of the registration.
+You can still use translatable strings like
+``LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.title``.
+Important to remember is that the :yaml:`widgetGroups`` property changed to :yaml:`groupNames`
+to stay consistent with other service registrations.
+
+In the following table you can see which WidgetType to use now based on the
+abstract you used previously.
+
++--------------------------------------+----------------------------------------------------------------------+
+| Previously used abstract             | Widget class to use for your registration                            |
++======================================+======================================================================+
+| :php:`AbstractBarChartWidget`        | :php:`TYPO3\CMS\Dashboard\Widgets\BarChartWidget`                    |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractChartWidget`           | This was only used as an abstract of the other chart widgets and is  |
+|                                      | not used anymore. If you want another graph type, you have to create |
+|                                      | your own widget.                                                     |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractCtaButtonWidget`       | :php:`TYPO3\CMS\Dashboard\Widgets\CtaWidget`                         |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractDoughnutChartWidget`   | :php:`TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget`               |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractListWidget`            | :php:`TYPO3\CMS\Dashboard\Widgets\ListWidget`                        |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractNumberWithIconWidget`  | :php:`TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget`              |
++--------------------------------------+----------------------------------------------------------------------+
+| :php:`AbstractRssWidget`             | :php:`TYPO3\CMS\Dashboard\Widgets\RssWidget`                         |
++--------------------------------------+----------------------------------------------------------------------+
+
+You can check the documentation of EXT:dashboard to see the exact options for every type of widget.
+
+Migration of widgets based on own widget type
+---------------------------------------------
+When you created your complete own widget type, the main thing to check is you
+use the Dependency Injection options you have now. Please check the documentation
+of EXT:dashboard to see how to create your own widget type and what options you
+have.
+
+.. index:: Backend, ext:dashboard, NotScanned
diff --git a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
index 47c683341d0f1b6775bc625f4d4efdcb2fa93f76..671b2278b109af253bfb862858099fe68333040f 100644
--- a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
+++ b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
@@ -27,15 +27,10 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Dashboard\Dashboard;
-use TYPO3\CMS\Dashboard\DashboardPreset;
+use TYPO3\CMS\Dashboard\DashboardInitializationService;
 use TYPO3\CMS\Dashboard\DashboardPresetRegistry;
 use TYPO3\CMS\Dashboard\DashboardRepository;
-use TYPO3\CMS\Dashboard\WidgetGroupRegistry;
-use TYPO3\CMS\Dashboard\WidgetRegistry;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalJavaScriptInterface;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
-use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
+use TYPO3\CMS\Dashboard\WidgetGroupInitializationService;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
@@ -56,36 +51,11 @@ class DashboardController extends AbstractController
      */
     protected $view;
 
-    /**
-     * @var array
-     */
-    protected $cssFiles = [];
-
-    /**
-     * @var array
-     */
-    protected $jsFiles = [];
-
-    /**
-     * @var mixed[]
-     */
-    protected $requireJsModules = [];
-
     /**
      * @var Dashboard
      */
     protected $currentDashboard;
 
-    /**
-     * @var Dashboard[]
-     */
-    protected $dashboardsForCurrentUser;
-
-    /**
-     * @var DashboardPreset[]
-     */
-    protected $availableDashboardPresets;
-
     /**
      * @var DashboardPresetRegistry
      */
@@ -97,76 +67,32 @@ class DashboardController extends AbstractController
     protected $dashboardRepository;
 
     /**
-     * @var WidgetGroupRegistry
+     * @var DashboardInitializationService
      */
-    protected $widgetGroupRepository;
+    private $dashboardInitializationService;
 
     /**
-     * @var WidgetRegistry
+     * @var WidgetGroupInitializationService
      */
-    protected $widgetRegistry;
-
-    /**
-     * @var ConfigurationManagerInterface
-     */
-    protected $configurationManager;
+    private $widgetGroupInitializationService;
 
     public function __construct(
         ModuleTemplate $moduleTemplate,
         UriBuilder $uriBuilder,
         DashboardPresetRegistry $dashboardPresetRepository,
         DashboardRepository $dashboardRepository,
-        WidgetGroupRegistry $widgetGroupRepository,
-        WidgetRegistry $widgetRegistry
+        DashboardInitializationService $dashboardInitializationService,
+        WidgetGroupInitializationService $widgetGroupInitializationService
     ) {
         $this->moduleTemplate = $moduleTemplate;
         $this->uriBuilder = $uriBuilder;
         $this->dashboardPresetRepository = $dashboardPresetRepository;
         $this->dashboardRepository = $dashboardRepository;
-        $this->widgetGroupRepository = $widgetGroupRepository;
-        $this->widgetRegistry = $widgetRegistry;
+        $this->dashboardInitializationService = $dashboardInitializationService;
 
-        $this->initializeDashboardsForCurrentUser();
-    }
-
-    protected function initializeDashboardsForCurrentUser(): void
-    {
-        $this->dashboardsForCurrentUser = $this->getDashboardsForCurrentUser();
-        $this->currentDashboard = $this->dashboardRepository->getDashboardByIdentifier($this->loadCurrentDashboard());
-
-        $this->availableDashboardPresets = $this->dashboardPresetRepository->getDashboardPresets();
-
-        if (empty($this->dashboardsForCurrentUser)) {
-            $this->dashboardsForCurrentUser = [];
-
-            $userConfig = $this->getBackendUser()->getTSConfig();
-            $dashboardsToCreate = GeneralUtility::trimExplode(
-                ',',
-                $userConfig['options.']['dashboard.']['dashboardPresetsForNewUsers'] ?? 'default'
-            );
-
-            /** @var DashboardPreset $dashboardPreset */
-            foreach ($this->availableDashboardPresets as $dashboardPreset) {
-                if (in_array($dashboardPreset->getIdentifier(), $dashboardsToCreate, true)) {
-                    $dashboard = $this->dashboardRepository->create(
-                        $dashboardPreset,
-                        (int)$this->getBackendUser()->user['uid']
-                    );
-
-                    if ($dashboard instanceof Dashboard) {
-                        $this->dashboardsForCurrentUser[$dashboard->getIdentifier()] = $dashboard;
-                    }
-                }
-            }
-        }
-
-        if (!$this->currentDashboard instanceof Dashboard) {
-            $this->currentDashboard = reset($this->dashboardsForCurrentUser);
-            $this->saveCurrentDashboard($this->currentDashboard->getIdentifier());
-        }
-
-        $this->currentDashboard->initializeWidgets();
-        $this->defineResourcesOfWidgets($this->currentDashboard->getWidgets());
+        $this->dashboardInitializationService->initializeDashboards($this->getBackendUser());
+        $this->currentDashboard = $this->dashboardInitializationService->getCurrentDashboard();
+        $this->widgetGroupInitializationService = $widgetGroupInitializationService;
     }
 
     /**
@@ -178,9 +104,9 @@ class DashboardController extends AbstractController
     public function mainAction(): void
     {
         $this->view->assignMultiple([
-            'availableDashboards' => $this->dashboardsForCurrentUser,
-            'dashboardPresets' => $this->availableDashboardPresets,
-            'widgetGroups' => $this->buildWidgetGroupsConfiguration(),
+            'availableDashboards' => $this->dashboardInitializationService->getDashboardsForUser(),
+            'dashboardPresets' => $this->dashboardPresetRepository->getDashboardPresets(),
+            'widgetGroups' => $this->widgetGroupInitializationService->buildWidgetGroupsConfiguration(),
             'currentDashboard' => $this->currentDashboard,
             'addWidgetUri' => (string)$this->uriBuilder->buildUriFromRoute('dashboard', ['action' => 'addWidget']),
             'addDashboardUri' => (string)$this->uriBuilder->buildUriFromRoute('dashboard', ['action' => 'addDashboard']),
@@ -333,17 +259,17 @@ class DashboardController extends AbstractController
      */
     protected function addFrontendResources(PageRenderer $pageRenderer): void
     {
-        foreach ($this->requireJsModules as $requireJsModule) {
+        foreach ($this->dashboardInitializationService->getRequireJsModules() as $requireJsModule) {
             if (is_array($requireJsModule)) {
                 $pageRenderer->loadRequireJsModule($requireJsModule[0], $requireJsModule[1]);
             } else {
                 $pageRenderer->loadRequireJsModule($requireJsModule);
             }
         }
-        foreach ($this->cssFiles as $cssFile) {
+        foreach ($this->dashboardInitializationService->getCssFiles() as $cssFile) {
             $pageRenderer->addCssFile($cssFile);
         }
-        foreach ($this->jsFiles as $jsFile) {
+        foreach ($this->dashboardInitializationService->getJsFiles() as $jsFile) {
             $pageRenderer->addJsFile($jsFile);
         }
     }
@@ -377,111 +303,4 @@ class DashboardController extends AbstractController
         $pageRenderer->loadRequireJsModule('TYPO3/CMS/Dashboard/DashboardDelete');
         $pageRenderer->addCssFile($publicResourcesPath . 'Css/dashboard.css');
     }
-
-    /**
-     * @return Dashboard[]
-     */
-    protected function getDashboardsForCurrentUser(): array
-    {
-        $dashboards = [];
-        foreach ($this->dashboardRepository->getDashboardsForUser((int)$this->getBackendUser()->user['uid']) as $dashboard) {
-            $dashboards[$dashboard->getIdentifier()] = $dashboard;
-        }
-        return $dashboards;
-    }
-
-    /**
-     * Define the different groups of widgets as shown in the modal when adding a widget to the current dashboard
-     *
-     * @return array
-     */
-    protected function buildWidgetGroupsConfiguration(): array
-    {
-        $groupConfigurations = [];
-        foreach ($this->widgetGroupRepository->getWidgetGroups() as $widgetGroup) {
-            $widgetInstances = [];
-            $widgetGroupIdentifier = $widgetGroup->getIdentifier();
-
-            $widgetsForGroup = $this->widgetRegistry->getAvailableWidgetsForWidgetGroup($widgetGroupIdentifier);
-            foreach ($widgetsForGroup as $identifier => $widgetService) {
-                $widgetInstances[$identifier] = GeneralUtility::makeInstance($widgetService);
-            }
-
-            $groupConfigurations[$widgetGroupIdentifier] = [
-                'identifier' => $widgetGroupIdentifier,
-                'title' => $widgetGroup->getTitle(),
-                'widgets' => $widgetInstances
-            ];
-        }
-
-        return $groupConfigurations;
-    }
-
-    /**
-     * @param array $widgets
-     */
-    protected function defineResourcesOfWidgets(array $widgets): void
-    {
-        foreach ($widgets as $widget) {
-            if ($widget instanceof RequireJsModuleInterface) {
-                $this->defineRequireJsModules($widget);
-            }
-            if ($widget instanceof AdditionalCssInterface) {
-                $this->defineCssFiles($widget);
-            }
-            if ($widget instanceof AdditionalJavaScriptInterface) {
-                $this->defineJsFiles($widget);
-            }
-        }
-    }
-
-    /**
-     * Add the RequireJS modules needed by some widgets
-     *
-     * @param RequireJsModuleInterface $widgetInstance
-     */
-    protected function defineRequireJsModules(RequireJsModuleInterface $widgetInstance): void
-    {
-        foreach ($widgetInstance->getRequireJsModules() as $moduleNameOrIndex => $callbackOrModuleName) {
-            if (is_string($moduleNameOrIndex)) {
-                $this->requireJsModules[] = [$moduleNameOrIndex, $callbackOrModuleName];
-            } else {
-                $this->requireJsModules[] = $callbackOrModuleName;
-            }
-        }
-    }
-
-    /**
-     * Define the correct path of the JS files of a widget and add them to the list of JS files that needs to be
-     * included
-     *
-     * @param AdditionalJavaScriptInterface $widgetInstance
-     */
-    protected function defineJsFiles(AdditionalJavaScriptInterface $widgetInstance): void
-    {
-        foreach ($widgetInstance->getJsFiles() as $key => $jsFile) {
-            if (strpos($jsFile, 'EXT:') === 0) {
-                $jsFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFile));
-            }
-            $this->jsFiles[$jsFile] = $jsFile;
-        }
-    }
-
-    /**
-     * Define the correct path of the CSS files of a widget and add them to the list of CSS files that needs to be
-     * included
-     *
-     * @param AdditionalCssInterface $widgetInstance
-     */
-    protected function defineCssFiles(AdditionalCssInterface $widgetInstance): void
-    {
-        foreach ($widgetInstance->getCssFiles() as $cssFile) {
-            if (strpos($cssFile, 'EXT:') === 0) {
-                $cssFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFile));
-            }
-            if (!in_array($cssFile, $this->cssFiles, true)) {
-                $this->cssFiles[$cssFile] = $cssFile;
-            }
-        }
-    }
 }
diff --git a/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php b/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php
index 1f3bc7b825a0b2140086794c27d80a022fdfcf80..dbe46bd9527a7312c44731d978c5f107d664e270 100644
--- a/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php
+++ b/typo3/sysext/dashboard/Classes/Controller/WidgetAjaxController.php
@@ -18,7 +18,6 @@ namespace TYPO3\CMS\Dashboard\Controller;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Http\JsonResponse;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Dashboard\Dashboard;
 use TYPO3\CMS\Dashboard\DashboardRepository;
 use TYPO3\CMS\Dashboard\WidgetRegistry;
@@ -58,23 +57,21 @@ class WidgetAjaxController extends AbstractController
     public function getContent(ServerRequestInterface $request): ResponseInterface
     {
         $queryParams = $request->getQueryParams();
-        $availableWidgets = $this->widgetRegistry->getAvailableWidgets();
 
-        if (empty((string)$queryParams['widget']) || !array_key_exists((string)$queryParams['widget'], $availableWidgets)) {
+        try {
+            $widgetObject = $this->widgetRegistry->getAvailableWidget((string)$queryParams['widget']);
+        } catch (\InvalidArgumentException $e) {
             return new JsonResponse(['error' => 'Widget is not available!']);
         }
 
-        $widgetObject = GeneralUtility::makeInstance($availableWidgets[(string)$queryParams['widget']]);
-
-        $eventData = $widgetObject instanceof EventDataInterface ? $widgetObject->getEventData() : [];
         if (!$widgetObject instanceof WidgetInterface) {
-            return new JsonResponse(['error' => 'Widget doesnt have a valid widget class']);
+            return new JsonResponse(['error' => 'Widget doesn\'t have a valid widget class']);
         }
 
         $data = [
             'widget' => $queryParams['widget'],
             'content' => $widgetObject->renderWidgetContent(),
-            'eventdata' => $eventData,
+            'eventdata' => $widgetObject instanceof EventDataInterface ? $widgetObject->getEventData() : [],
         ];
 
         return new JsonResponse($data);
diff --git a/typo3/sysext/dashboard/Classes/Dashboard.php b/typo3/sysext/dashboard/Classes/Dashboard.php
index e9ea58199151fa48a2e750930f4bd0cc4bf6a68f..df66eeaf7cffd44b6330bbad565ad32269aa5e36 100644
--- a/typo3/sysext/dashboard/Classes/Dashboard.php
+++ b/typo3/sysext/dashboard/Classes/Dashboard.php
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Dashboard;
 
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
 use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
 
 class Dashboard
@@ -105,11 +106,9 @@ class Dashboard
     {
         $availableWidgets = $this->widgetRegistry->getAvailableWidgets();
         foreach ($this->widgetConfig as $hash => $widgetConfig) {
+            /* @var WidgetConfigurationInterface $widgetConfig */
             if (array_key_exists($widgetConfig['identifier'], $availableWidgets)) {
-                $widgetObject = $this->container->get($availableWidgets[$widgetConfig['identifier']]);
-                if ($widgetObject instanceof WidgetInterface) {
-                    $this->widgets[$hash] = $widgetObject;
-                }
+                $this->widgets[$hash] = $availableWidgets[$widgetConfig['identifier']];
             }
         }
     }
diff --git a/typo3/sysext/dashboard/Classes/DashboardInitializationService.php b/typo3/sysext/dashboard/Classes/DashboardInitializationService.php
new file mode 100644
index 0000000000000000000000000000000000000000..088e2c4177f88cc26435a14c294d38441c8ddcb5
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/DashboardInitializationService.php
@@ -0,0 +1,235 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Dashboard;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalJavaScriptInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
+
+class DashboardInitializationService
+{
+    protected const MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER = 'dashboard/current_dashboard/';
+
+    /**
+     * @var DashboardRepository
+     */
+    private $dashboardRepository;
+
+    /**
+     * @var DashboardPresetRegistry
+     */
+    private $dashboardPresetRegistry;
+
+    /**
+     * @var Dashboard
+     */
+    private $currentDashboard;
+
+    /**
+     * @var BackendUserAuthentication
+     */
+    private $user;
+
+    /**
+     * @var array
+     */
+    private $requireJsModules = [];
+    private $jsFiles = [];
+    private $cssFiles = [];
+
+    public function __construct(
+        DashboardRepository $dashboardRepository,
+        DashboardPresetRegistry $dashboardPresetRegistry
+    ) {
+        $this->dashboardRepository = $dashboardRepository;
+        $this->dashboardPresetRegistry = $dashboardPresetRegistry;
+    }
+
+    public function initializeDashboards(BackendUserAuthentication $user): void
+    {
+        $this->user = $user;
+        $this->currentDashboard = $this->defineCurrentDashboard();
+
+        $this->currentDashboard->initializeWidgets();
+        $this->defineResourcesOfWidgets($this->currentDashboard->getWidgets());
+    }
+
+    public function getCurrentDashboard(): Dashboard
+    {
+        return $this->currentDashboard;
+    }
+
+    protected function defineCurrentDashboard(): Dashboard
+    {
+        $currentDashboard = $this->dashboardRepository->getDashboardByIdentifier($this->loadCurrentDashboard($this->user));
+        if (!$currentDashboard instanceof Dashboard) {
+            $dashboards = $this->getDashboardsForUser();
+            $currentDashboard = reset($dashboards);
+            $this->saveCurrentDashboard($this->user, $currentDashboard->getIdentifier());
+        }
+
+        return $currentDashboard;
+    }
+
+    protected function createDefaultDashboards(): array
+    {
+        $dashboardsForUser = [];
+
+        $userConfig = $this->user->getTSConfig();
+        $dashboardsToCreate = GeneralUtility::trimExplode(
+            ',',
+            $userConfig['options.']['dashboard.']['dashboardPresetsForNewUsers'] ?? 'default'
+        );
+
+        /** @var DashboardPreset $dashboardPreset */
+        foreach ($this->dashboardPresetRegistry->getDashboardPresets() as $dashboardPreset) {
+            if (in_array($dashboardPreset->getIdentifier(), $dashboardsToCreate, true)) {
+                $dashboard = $this->dashboardRepository->create(
+                    $dashboardPreset,
+                    (int)$this->user->user['uid']
+                );
+
+                if ($dashboard instanceof Dashboard) {
+                    $dashboardsForUser[$dashboard->getIdentifier()] = $dashboard;
+                }
+            }
+        }
+
+        return $dashboardsForUser;
+    }
+
+    /**
+     * @return Dashboard[]
+     */
+    public function getDashboardsForUser(): array
+    {
+        $dashboards = [];
+        foreach ($this->dashboardRepository->getDashboardsForUser((int)$this->user->user['uid']) as $dashboard) {
+            $dashboards[$dashboard->getIdentifier()] = $dashboard;
+        }
+
+        if ($dashboards === []) {
+            $dashboards = $this->createDefaultDashboards();
+        }
+
+        return $dashboards;
+    }
+
+    /**
+     * @param array $widgets
+     */
+    protected function defineResourcesOfWidgets(array $widgets): void
+    {
+        foreach ($widgets as $widget) {
+            $concreteInstance = GeneralUtility::makeInstance($widget->getServiceName());
+            if ($concreteInstance instanceof RequireJsModuleInterface) {
+                $this->defineRequireJsModules($concreteInstance);
+            }
+            if ($concreteInstance instanceof AdditionalCssInterface) {
+                $this->defineCssFiles($concreteInstance);
+            }
+            if ($concreteInstance instanceof AdditionalJavaScriptInterface) {
+                $this->defineJsFiles($concreteInstance);
+            }
+        }
+    }
+
+    /**
+     * Add the RequireJS modules needed by some widgets
+     *
+     * @param RequireJsModuleInterface $widgetInstance
+     */
+    protected function defineRequireJsModules(RequireJsModuleInterface $widgetInstance): void
+    {
+        foreach ($widgetInstance->getRequireJsModules() as $moduleNameOrIndex => $callbackOrModuleName) {
+            if (is_string($moduleNameOrIndex)) {
+                $this->requireJsModules[] = [$moduleNameOrIndex, $callbackOrModuleName];
+            } else {
+                $this->requireJsModules[] = $callbackOrModuleName;
+            }
+        }
+    }
+
+    /**
+     * Define the correct path of the JS files of a widget and add them to the list of JS files that needs to be
+     * included
+     *
+     * @param AdditionalJavaScriptInterface $widgetInstance
+     */
+    protected function defineJsFiles(AdditionalJavaScriptInterface $widgetInstance): void
+    {
+        foreach ($widgetInstance->getJsFiles() as $jsFile) {
+            if (strpos($jsFile, 'EXT:') === 0) {
+                $jsFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFile));
+            }
+            $this->jsFiles[$jsFile] = $jsFile;
+        }
+    }
+
+    /**
+     * Define the correct path of the CSS files of a widget and add them to the list of CSS files that needs to be
+     * included
+     *
+     * @param AdditionalCssInterface $widgetInstance
+     */
+    protected function defineCssFiles(AdditionalCssInterface $widgetInstance): void
+    {
+        foreach ($widgetInstance->getCssFiles() as $cssFile) {
+            if (strpos($cssFile, 'EXT:') === 0) {
+                $cssFile = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFile));
+            }
+            $this->cssFiles[$cssFile] = $cssFile;
+        }
+    }
+
+    protected function loadCurrentDashboard(BackendUserAuthentication $user): string
+    {
+        return $user->getModuleData(self::MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER) ?? '';
+    }
+
+    protected function saveCurrentDashboard(BackendUserAuthentication $user, string $identifier): void
+    {
+        $user->pushModuleData(self::MODULE_DATA_CURRENT_DASHBOARD_IDENTIFIER, $identifier);
+    }
+
+    /**
+     * @return array
+     */
+    public function getRequireJsModules(): array
+    {
+        return $this->requireJsModules;
+    }
+
+    /**
+     * @return array
+     */
+    public function getJsFiles(): array
+    {
+        return $this->jsFiles;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCssFiles(): array
+    {
+        return $this->cssFiles;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php b/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php
index 47f0b475d7dbb4627d8f9776bfbcc882c9896447..f22edef4d98094d1a5563c2da104ff12813b1ddc 100644
--- a/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php
+++ b/typo3/sysext/dashboard/Classes/DependencyInjection/DashboardWidgetPass.php
@@ -17,8 +17,11 @@ namespace TYPO3\CMS\Dashboard\DependencyInjection;
 
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Dashboard\WidgetRegistry;
+use TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration;
 
 /**
  * @internal
@@ -44,6 +47,7 @@ final class DashboardWidgetPass implements CompilerPassInterface
     public function process(ContainerBuilder $container): void
     {
         $widgetRegistryDefinition = $container->findDefinition(WidgetRegistry::class);
+        /* @var Definition|bool $widgetRegistryDefinition */
         if (!$widgetRegistryDefinition) {
             return;
         }
@@ -51,19 +55,72 @@ final class DashboardWidgetPass implements CompilerPassInterface
         foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
             $definition = $container->findDefinition($serviceName);
             $definition->setPublic(true);
-            // Widgets are handled like prototypes right now (will require state)
-            // @todo: Widgets should preferably be services, but that will require WidgetInterface
-            // to change
-            $definition->setShared(false);
-            $className = $definition->getClass() ?? $serviceName;
+
             foreach ($tags as $attributes) {
                 $identifier = $attributes['identifier'] ?? $serviceName;
-                $widgetRegistryDefinition->addMethodCall('registerWidget', [
+                $attributes['identifier'] = $identifier;
+                $attributes['serviceName'] = $serviceName;
+                $attributes = $this->convertAttributes($attributes);
+
+                $configurationServiceName = $this->registerWidgetConfigurationService(
+                    $container,
                     $identifier,
-                    $serviceName,
-                    GeneralUtility::trimExplode(',', $attributes['widgetGroups'] ?? '', true)
-                ]);
+                    $attributes
+                );
+                $definition->setArgument('$configuration', new Reference($configurationServiceName));
+
+                $widgetRegistryDefinition->addMethodCall('registerWidget', [$identifier . 'WidgetConfiguration']);
             }
         }
     }
+
+    private function convertAttributes(array $attributes): array
+    {
+        $attributes = array_merge([
+            'iconIdentifier' => 'content-dashboard',
+            'height' => 'small',
+            'width' => 'small',
+        ], $attributes);
+
+        if (isset($attributes['groupNames'])) {
+            $attributes['groupNames'] = GeneralUtility::trimExplode(',', $attributes['groupNames'], true);
+        } else {
+            $attributes['groupNames'] = [];
+        }
+
+        if (isset($attributes['additionalCssClasses'])) {
+            $attributes['additionalCssClasses'] = GeneralUtility::trimExplode(' ', $attributes['additionalCssClasses'], true);
+        } else {
+            $attributes['additionalCssClasses'] = [];
+        }
+
+        return $attributes;
+    }
+
+    private function registerWidgetConfigurationService(
+        ContainerBuilder $container,
+        string $widgetIdentifier,
+        array $arguments
+    ): string {
+        $serviceName = $widgetIdentifier . 'WidgetConfiguration';
+
+        $definition = new Definition(
+            WidgetConfiguration::class,
+            $this->adjustArgumentsForDi($arguments)
+        );
+        $definition->setPublic(true);
+        $container->addDefinitions([$serviceName => $definition]);
+
+        return $serviceName;
+    }
+
+    private function adjustArgumentsForDi(array $arguments): array
+    {
+        foreach ($arguments as $key => $value) {
+            $arguments['$' . $key] = $value;
+            unset($arguments[$key]);
+        }
+
+        return $arguments;
+    }
 }
diff --git a/typo3/sysext/dashboard/Classes/ServiceProvider.php b/typo3/sysext/dashboard/Classes/ServiceProvider.php
index 52f06253759e426fee6e0efeaf5693d22981d104..51784438545d7c9601787a8b91463eec5801d509 100644
--- a/typo3/sysext/dashboard/Classes/ServiceProvider.php
+++ b/typo3/sysext/dashboard/Classes/ServiceProvider.php
@@ -131,32 +131,6 @@ class ServiceProvider extends AbstractServiceProvider
         return $widgetGroupRegistry;
     }
 
-    public static function configureWidgetRegistry(
-        ContainerInterface $container,
-        WidgetRegistry $widgetRegistry = null
-    ): WidgetRegistry {
-        $widgetRegistry = $widgetRegistry ?? self::new($container, WidgetRegistry::class);
-        $cache = $container->get('cache.core');
-
-        $cacheIdentifier = 'Dashboard_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'Widgets');
-        if ($cache->has($cacheIdentifier)) {
-            $widgetsFromPackages = $cache->require($cacheIdentifier);
-        } else {
-            $widgetsFromPackages = $container->get('dashboard.widgets')->getArrayCopy();
-            $cache->set($cacheIdentifier, 'return ' . var_export($widgetsFromPackages, true) . ';');
-        }
-
-        foreach ($widgetsFromPackages as $identifier => $options) {
-            $widgetRegistry->registerWidget(
-                $identifier,
-                $options['class'],
-                (array)$options['widgetGroups']
-            );
-        }
-
-        return $widgetRegistry;
-    }
-
     /**
      * @param ContainerInterface $container
      * @param ArrayObject $presets
diff --git a/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php b/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php
new file mode 100644
index 0000000000000000000000000000000000000000..890cc98ade0f2b81a0427a3e8b7db823676934ab
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Utility/ButtonUtility.php
@@ -0,0 +1,34 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Utility;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+
+class ButtonUtility
+{
+    public static function generateButtonConfig(?ButtonProviderInterface $buttonProvider): array
+    {
+        if ($buttonProvider instanceof ButtonProviderInterface && $buttonProvider->getTitle() && $buttonProvider->getLink()) {
+            return [
+                'text' => $buttonProvider->getTitle(),
+                'link' => $buttonProvider->getLink(),
+                'target' => $buttonProvider->getTarget(),
+            ];
+        }
+
+        return [];
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Views/Factory.php b/typo3/sysext/dashboard/Classes/Views/Factory.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcc072325696b8fb21741a76518083815417405b
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Views/Factory.php
@@ -0,0 +1,30 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Views;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+class Factory
+{
+    public static function widgetTemplate(): StandaloneView
+    {
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('dashboard');
+
+        return $view;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/WidgetApi.php b/typo3/sysext/dashboard/Classes/WidgetApi.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fce884df3658b426cf7e582933d1245c2682ecf
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/WidgetApi.php
@@ -0,0 +1,38 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard;
+
+/*
+ * 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!
+ */
+
+/**
+ * Provides API for widgets.
+ */
+class WidgetApi
+{
+    /**
+     * Provides default colors to use for charts.
+     *
+     * @return array Hex codes of default colors.
+     */
+    public static function getDefaultChartColors(): array
+    {
+        return [
+            '#ff8700',
+            '#a4276a',
+            '#1a568f',
+            '#4c7e3a',
+            '#69bbb5',
+        ];
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php b/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php
new file mode 100644
index 0000000000000000000000000000000000000000..8fec06754d78e11e16466435b884827c9380af74
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/WidgetGroupInitializationService.php
@@ -0,0 +1,73 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Localization\LanguageService;
+
+class WidgetGroupInitializationService
+{
+    /**
+     * @var WidgetGroupRegistry
+     */
+    private $widgetGroupRegistry;
+
+    /**
+     * @var WidgetRegistry
+     */
+    private $widgetRegistry;
+
+    public function __construct(WidgetGroupRegistry $widgetGroupRegistry, WidgetRegistry $widgetRegistry)
+    {
+        $this->widgetGroupRegistry = $widgetGroupRegistry;
+        $this->widgetRegistry = $widgetRegistry;
+    }
+
+    /**
+     * Define the different groups of widgets as shown in the modal when adding a widget to the current dashboard
+     *
+     * @return array
+     */
+    public function buildWidgetGroupsConfiguration(): array
+    {
+        $groupConfigurations = [];
+        foreach ($this->widgetGroupRegistry->getWidgetGroups() as $widgetGroup) {
+            $widgets = [];
+            $widgetGroupIdentifier = $widgetGroup->getIdentifier();
+
+            $widgetsForGroup = $this->widgetRegistry->getAvailableWidgetsForWidgetGroup($widgetGroupIdentifier);
+            foreach ($widgetsForGroup as $widgetConfiguration) {
+                $widgets[$widgetConfiguration->getIdentifier()] = [
+                    'iconIdentifier' => $widgetConfiguration->getIconIdentifier(),
+                    'title' => $this->getLanguageService()->sl($widgetConfiguration->getTitle()),
+                    'description' => $this->getLanguageService()->sl($widgetConfiguration->getDescription()),
+                ];
+            }
+
+            $groupConfigurations[$widgetGroupIdentifier] = [
+                'identifier' => $widgetGroupIdentifier,
+                'title' => $widgetGroup->getTitle(),
+                'widgets' => $widgets,
+            ];
+        }
+
+        return $groupConfigurations;
+    }
+
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/WidgetRegistry.php b/typo3/sysext/dashboard/Classes/WidgetRegistry.php
index f5cd73138292acd75db4197b87e45253e577d589..97172c257a2e3a3177d38fb5cec60142a901d98e 100644
--- a/typo3/sysext/dashboard/Classes/WidgetRegistry.php
+++ b/typo3/sysext/dashboard/Classes/WidgetRegistry.php
@@ -18,6 +18,7 @@ namespace TYPO3\CMS\Dashboard;
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
 
 class WidgetRegistry implements SingletonInterface
@@ -52,6 +53,18 @@ class WidgetRegistry implements SingletonInterface
         return $this->widgets;
     }
 
+    /**
+     * @throws \InvalidArgumentException If requested identifier does not exist.
+     */
+    public function getAvailableWidget(string $identifier): WidgetInterface
+    {
+        if (array_key_exists($identifier, $this->getAvailableWidgets())) {
+            return $this->container->get($this->widgets[$identifier]->getServiceName());
+        }
+
+        throw new \InvalidArgumentException('Requested widget "' . $identifier . '" does not exist.', 1584777201);
+    }
+
     public function getAvailableWidgetsForWidgetGroup(string $widgetGroupIdentifier): array
     {
         if (!array_key_exists($widgetGroupIdentifier, $this->widgetsPerWidgetGroup)) {
@@ -60,36 +73,40 @@ class WidgetRegistry implements SingletonInterface
         return $this->checkPermissionOfWidgets($this->widgetsPerWidgetGroup[$widgetGroupIdentifier]);
     }
 
-    public function registerWidget(string $identifier, string $widgetServiceName, array $widgetGroupIdentifiers): void
+    public function registerWidget(string $serviceName): void
     {
-        $this->widgets[$identifier] = $widgetServiceName;
-        foreach ($widgetGroupIdentifiers as $widgetGroupIdentifier) {
-            $this->widgetsPerWidgetGroup[$widgetGroupIdentifier][$identifier] = $widgetServiceName;
+        $widgetConfiguration = $this->container->get($serviceName);
+        $this->widgets[$widgetConfiguration->getIdentifier()] = $widgetConfiguration;
+        foreach ($widgetConfiguration->getGroupNames() as $groupIdentifier) {
+            $this->widgetsPerWidgetGroup = ArrayUtility::setValueByPath(
+                $this->widgetsPerWidgetGroup,
+                $groupIdentifier . '/' . $widgetConfiguration->getIdentifier(),
+                $widgetConfiguration
+            );
         }
     }
 
     protected function checkPermissionOfWidgets(array $widgets): array
     {
-        return array_filter($widgets, function ($widgetIdentifier) {
-            return $this->getBackendUser()->check('available_widgets', $widgetIdentifier);
+        return array_filter($widgets, function ($identifier) {
+            return $this->getBackendUser()->check('available_widgets', $identifier);
         }, ARRAY_FILTER_USE_KEY);
     }
 
-    protected function getBackendUser(): BackendUserAuthentication
+    public function widgetItemsProcFunc(array &$parameters): void
     {
-        return $GLOBALS['BE_USER'];
-    }
-
-    public function widgetItemsProcFunc(array $parameters): void
-    {
-        foreach ($this->widgets as $identifier => $widget) {
-            $widgetObject = $this->container->get($widget);
+        foreach ($this->widgets as $widget) {
             $parameters['items'][] = [
-                $widgetObject->getTitle() ,
-                $identifier,
-                $widgetObject->getIconIdentifier() ?? 'content-dashboard',
-                $widgetObject->getDescription()
+                $widget->getTitle(),
+                $widget->getIdentifier(),
+                $widget->getIconIdentifier(),
+                $widget->getDescription(),
             ];
         }
     }
+
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php
deleted file mode 100644
index 875dd583f1a37bccd9dcaa10520f185ada24a569..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractBarChartWidget.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * The AbstractBarChartWidget class is the basic widget class for bar charts.
- * It is possible to extend this class for custom widgets.
- * In your class you have to store the data to display in $this->chartData.
- * More information can be found in the documentation.
- */
-abstract class AbstractBarChartWidget extends AbstractChartWidget
-{
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-chart-bar';
-
-    /**
-     * @var string
-     */
-    protected $chartType = 'bar';
-
-    /**
-     * @var mixed[]
-     */
-    protected $chartOptions = [
-        'maintainAspectRatio' => false,
-        'legend' => [
-            'display' => false
-        ],
-        'scales' => [
-            'yAxes' => [
-                [
-                    'ticks' => [
-                        'beginAtZero' => true
-                    ]
-                ]
-            ],
-            'xAxes' => [
-                [
-                    'ticks' => [
-                        'maxTicksLimit' => 15
-                    ]
-                ]
-            ]
-        ]
-    ];
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php
deleted file mode 100644
index 7e93c6bd067f9b3860c4366eabf2af8825ef31b5..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractChartWidget.php
+++ /dev/null
@@ -1,174 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
-
-/**
- * The AbstractChartWidget class is the basic widget class for all chart widgets.
- * It is possible to extend this class for custom widgets. EXT:dashboard also provides
- * more special chart types for widgets (bar chart and doughnut chart).
- */
-abstract class AbstractChartWidget extends AbstractWidget implements AdditionalCssInterface, RequireJsModuleInterface, EventDataInterface
-{
-    /**
-     * The type of chart you want to show. The types of charts that are available can be found in the
-     * chart.js documentation.
-     *
-     * @link https://www.chartjs.org/docs/latest/charts/
-     *
-     * Currently the following chart types are implemented:
-     * @see AbstractBarChartWidget
-     * @see AbstractDoughnutChartWidget
-     *
-     * @var string
-     */
-    protected $chartType = '';
-
-    /**
-     * This property should contain the data for the graph. The data and options you have depend on the type
-     * of chart. More information can be found in the documentation of the specific type.
-     *
-     * @link https://www.chartjs.org/docs/latest/charts/bar.html#data-structure
-     * @link https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure
-     *
-     * @var array
-     */
-    protected $chartData = [];
-
-    /**
-     * This property should contain the options to configure your graph. The options available are based on the type
-     * of chart. The implementations of the charts will contain the default options for that type of graph.
-     *
-     * @see AbstractBarChartWidget::$chartOptions
-     * @see AbstractDoughnutChartWidget::$chartOptions
-     *
-     * @var array
-     */
-    protected $chartOptions = [];
-
-    /**
-     * This property can be used to pass data to the JavaScript that will handle the content rendering. For
-     * charts, the only property necessary for charts is the graphConfig element. This will be set in the
-     * getEventData method of this class. Setting this property manually is therefore not needed.
-     *
-     * @see getEventData
-     *
-     * @var array
-     */
-    protected $eventData = [];
-
-    /**
-     * The default colors that will be used for the graphs.
-     *
-     * @var string[]
-     */
-    protected $chartColors = ['#ff8700', '#a4276a', '#1a568f', '#4c7e3a', '#69bbb5'];
-
-    /**
-     * If you want to show a button below the graph, you need to set the title and the link to the button.
-     * This text can be a fixed string or can contain a translatable string.
-     *
-     * @var string
-     */
-    protected $buttonText = '';
-
-    /**
-     * The link of the button. Besides the link, also the buttonText should be set before the buttons will be
-     * rendered.
-     *
-     * @var string
-     */
-    protected $buttonLink = '';
-
-    /**
-     * By default the link of the button will be opened in the current frame. By setting the target, you can specify
-     * where the link should be opened.
-     *
-     * @var string
-     */
-    protected $buttonTarget = '';
-
-    /**
-     * This CSS class is used so the JavaScript can identify the widgets containing graphs. Overriding this
-     * property could lead to the widget not working.
-     *
-     * @internal
-     *
-     * @var string
-     */
-    protected $additionalClasses = 'dashboard-item--chart';
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-chart';
-
-    /**
-     * @var string
-     */
-    protected $templateName = 'ChartWidget';
-
-    /**
-     * This method is used to define the data that will be shown in the graph. This method should be implemented
-     * and should set the data in the chartData property.
-     *
-     * @see chartData
-     */
-    abstract protected function prepareChartData(): void;
-
-    protected function initializeView(): void
-    {
-        parent::initializeView();
-
-        if ($this->buttonLink && $this->buttonText) {
-            $this->view->assign(
-                'button',
-                [
-                    'text' => $this->getLanguageService()->sL($this->buttonText) ?: $this->buttonText,
-                    'link' => $this->buttonLink,
-                    'target' => $this->buttonTarget
-                ]
-            );
-        }
-    }
-
-    public function getEventData(): array
-    {
-        $this->prepareChartData();
-        $this->eventData['graphConfig'] = [
-            'type' => $this->chartType,
-            'data' => $this->chartData,
-            'options' => $this->chartOptions
-        ];
-        return $this->eventData;
-    }
-
-    public function getCssFiles(): array
-    {
-        return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css'];
-    }
-
-    public function getRequireJsModules(): array
-    {
-        return [
-            'TYPO3/CMS/Dashboard/Contrib/chartjs',
-            'TYPO3/CMS/Dashboard/ChartInitializer',
-        ];
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php
deleted file mode 100644
index 7ec370a767d9e8143b880e995354e7fff0e9a813..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractCtaButtonWidget.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * The AbstractCtaButtonWidget class is the basic widget class for simple CTA widgets.
- * It is possible to extend this class for custom widgets.
- * Simply overwrite $this->link and $this->label to make use of this widget type.
- */
-abstract class AbstractCtaButtonWidget extends AbstractWidget
-{
-    /**
-     * This link will be the main data in the widget
-     *
-     * @var string
-     */
-    protected $link = '';
-
-    /**
-     * When filled, this is used as the button label
-     *
-     * @var string
-     */
-    protected $label = '';
-
-    /**
-     * When filled, a text is shown above the link
-     *
-     * @var string
-     */
-    protected $text = '';
-
-    /**
-     * This property contains the identifier of the icon that should be shown in the widget
-     *
-     * @var string
-     */
-    protected $icon;
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-calltoaction';
-
-    /**
-     * @var string
-     */
-    protected $templateName = 'CtaWidget';
-
-    public function __construct(string $identifier)
-    {
-        parent::__construct($identifier);
-        $this->height = 1;
-
-        $this->view->assignMultiple([
-            'link' => $this->link,
-            'label' => $this->label,
-            'icon' => $this->icon,
-            'text' => $this->getLanguageService()->sL($this->text)
-        ]);
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php
deleted file mode 100644
index c5b5b79f70f174bc231f07e3fd5bc71b890c1653..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractDoughnutChartWidget.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * The AbstractDoughnutChartWidget class is the basic widget class for doughnut charts.
- * It is possible to extend this class for custom widgets.
- * In your class you have to store the data to display in $this->chartData.
- * More information can be found in the documentation.
- */
-abstract class AbstractDoughnutChartWidget extends AbstractChartWidget
-{
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-chart-pie';
-
-    /**
-     * @var string
-     */
-    protected $chartType = 'doughnut';
-
-    /**
-     * @var mixed[]
-     */
-    protected $chartOptions = [
-        'maintainAspectRatio' => false,
-        'legend' => [
-            'display' => true,
-            'position' => 'bottom'
-        ],
-        'cutoutPercentage' => 60
-    ];
-
-    /**
-     * @var int
-     */
-    protected $height = 4;
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php
deleted file mode 100644
index 09f3d1ed228100fddc4f8b0505616f2185f27ca1..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractListWidget.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * The AbstractListWidget class is the basic widget class for structured content.
- * It is possible to extend this class for custom widgets.
- * In your class you have to set $this->items with the data to display.
- */
-abstract class AbstractListWidget extends AbstractWidget
-{
-    /**
-     * This property should contain the items to be displayed
-     *
-     * @var array
-     */
-    protected $items = [];
-
-    /**
-     * Limit the amount of items to be displayed
-     *
-     * @var int
-     */
-    protected $limit = 5;
-
-    /**
-     * Link to e.g. an external resource with the full items list
-     *
-     * @var string
-     */
-    protected $moreItemsLink = '';
-
-    /**
-     * This should be the text for the more items link
-     *
-     * @var string
-     */
-    protected $moreItemsText = '';
-
-    /**
-     * @var int
-     */
-    protected $height = 4;
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-list';
-
-    /**
-     * @var string
-     */
-    protected $templateName = 'ListWidget';
-
-    public function renderWidgetContent(): string
-    {
-        $this->view->assign('items', $this->items);
-        $this->view->assign('moreItemsLink', $this->moreItemsLink);
-        $this->view->assign('moreItemsText', $this->moreItemsText);
-        return $this->view->render();
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php
deleted file mode 100644
index cb1672939f385b50e814008c6c586c95bd11d1e9..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractNumberWithIconWidget.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * The AbstractNumberWithIconWidget class is the basic widget class to display a number next to an icon.
- * It is possible to extend this class for own widgets.
- * Simply overwrite $this->subtitle, $this->number and $this->icon to make use of this widget type.
- */
-abstract class AbstractNumberWithIconWidget extends AbstractWidget
-{
-    /**
-     * When filled, a subtitle is shown below the title
-     *
-     * @var string
-     */
-    protected $subtitle;
-
-    /**
-     * This number will be the main data in the widget
-     *
-     * @var int
-     */
-    protected $number;
-
-    /**
-     * This property contains the identifier of the icon that should be shown in the widget
-     *
-     * @var string
-     */
-    protected $icon;
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-number';
-
-    /**
-     * @var string
-     */
-    protected $templateName = 'NumberWithIconWidget';
-
-    protected function initializeView(): void
-    {
-        parent::initializeView();
-        $this->view->assign('icon', $this->icon);
-        $this->view->assign('subtitle', $this->getSubTitle());
-        $this->view->assign('number', $this->number);
-    }
-
-    public function getSubTitle(): string
-    {
-        return $this->getLanguageService()->sL($this->subtitle) ?: $this->subtitle;
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php
deleted file mode 100644
index 55a2be2983e15f1a81bf306a6f6dec18f5b22d22..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractRssWidget.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-use RuntimeException;
-use TYPO3\CMS\Core\Cache\CacheManager;
-use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * The AbstractRssWidget class is the basic widget class to display items from a RSS feed.
- * It is possible to extend this class for custom widgets.
- * In your class you have to set $this->rssFile with the URL to the feed.
- */
-abstract class AbstractRssWidget extends AbstractListWidget
-{
-    /**
-     * The rss file (resource) the data should be fetched from
-     *
-     * @var string
-     */
-    protected $rssFile = '';
-
-    /**
-     * Lifetime of the items cache
-     *
-     * @var int
-     */
-    protected $lifeTime = 900;
-
-    /**
-     * @var int
-     */
-    protected $height = 6;
-
-    /**
-     * @var int
-     */
-    protected $width = 4;
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-rss';
-
-    /**
-     * @var string
-     */
-    protected $templateName = 'RssWidget';
-
-    /**
-     * @var FrontendInterface
-     */
-    protected $cache;
-
-    public function __construct(string $identifier)
-    {
-        AbstractListWidget::__construct($identifier);
-        $this->cache = GeneralUtility::makeInstance(CacheManager::class)
-            ->getCache('dashboard_rss');
-        $this->loadRssFeed();
-    }
-
-    protected function loadRssFeed(): void
-    {
-        $cacheHash = md5($this->rssFile);
-        if ($this->items = $this->cache->get($cacheHash)) {
-            return;
-        }
-
-        $rssContent = GeneralUtility::getUrl($this->rssFile);
-        if ($rssContent === false) {
-            throw new RuntimeException('RSS URL could not be fetched', 1573385431);
-        }
-        $rssFeed = simplexml_load_string($rssContent);
-        $itemCount = 0;
-        foreach ($rssFeed->channel->item as $item) {
-            if ($itemCount < $this->limit) {
-                $this->items[] = [
-                    'title' => (string)$item->title,
-                    'link' => trim((string)$item->link),
-                    'pubDate' => (string)$item->pubDate,
-                    'description' => (string)$item->description,
-                ];
-            } else {
-                continue;
-            }
-            $itemCount++;
-        }
-        $this->cache->set($cacheHash, $this->items, ['dashboard_rss'], $this->lifeTime);
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php b/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php
deleted file mode 100644
index c55f7f7c04d765f881e4f483f3b112023bad422d..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/AbstractWidget.php
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
-use TYPO3\CMS\Fluid\View\StandaloneView;
-use TYPO3Fluid\Fluid\View\ViewInterface;
-
-/**
- * The AbstractWidget class is the basic widget class for all widgets.
- * It is possible to extend this class for custom widgets, but EXT:dashboard provides
- * some more specific types of widgets to extend from. For more details, please check:
- *
- * @see AbstractBarChartWidget
- * @see AbstractChartWidget
- * @see AbstractCtaButtonWidget
- * @see AbstractDoughnutChartWidget
- * @see AbstractListWidget
- * @see AbstractNumberWithIconWidget
- * @see AbstractRssWidget
- */
-abstract class AbstractWidget implements WidgetInterface
-{
-    /**
-     * The unique identifier of the widget
-     *
-     * @var string
-     */
-    protected $identifier;
-
-    /**
-     * The title is used for the widget selector
-     *
-     * @var string
-     */
-    protected $title;
-
-    /**
-     * The description is used for the widget selector
-     *
-     * @var string
-     */
-    protected $description = '';
-
-    /**
-     * The height of the widget in rows (1-6)
-     *
-     * @var int
-     */
-    protected $height = 2;
-
-    /**
-     * The width of the widget in rows (1-4)
-     *
-     * @var int
-     */
-    protected $width = 2;
-
-    /**
-     * The icon identifier is used for the widget selector
-     *
-     * @var string
-     */
-    protected $iconIdentifier = '';
-
-    /**
-     * The template name of the widget
-     *
-     * @var string
-     */
-    protected $templateName = 'Widget';
-
-    /**
-     * Additional CSS classes which should be added to the rendered widget
-     *
-     * @var string
-     */
-    protected $additionalClasses = '';
-
-    /**
-     * @var ViewInterface
-     */
-    protected $view;
-
-    public function __construct(string $identifier)
-    {
-        $this->identifier = $identifier;
-
-        $this->initializeView();
-    }
-
-    public function getIdentifier(): string
-    {
-        return $this->identifier;
-    }
-
-    public function getTitle(): string
-    {
-        return $this->getLanguageService()->sL($this->title) ?: $this->title;
-    }
-
-    public function getDescription(): string
-    {
-        return $this->getLanguageService()->sL($this->description) ?: $this->description;
-    }
-
-    public function getIconIdentifier(): string
-    {
-        return $this->iconIdentifier;
-    }
-
-    public function getHeight(): int
-    {
-        return $this->height;
-    }
-
-    public function getWidth(): int
-    {
-        return $this->width;
-    }
-
-    public function renderWidgetContent(): string
-    {
-        return $this->view->render();
-    }
-
-    public function getAdditionalClasses(): string
-    {
-        return $this->additionalClasses;
-    }
-
-    protected function initializeView(): void
-    {
-        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
-        $this->view->setTemplate('Widget/' . $this->templateName);
-
-        $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%m-%d-%Y' : '%d-%m-%Y';
-        $this->view->assign('dateFormat', $dateFormat);
-
-        $this->view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('dashboard');
-
-        $this->view->assign('title', $this->getTitle());
-    }
-
-    protected function getLanguageService(): LanguageService
-    {
-        return $GLOBALS['LANG'];
-    }
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..66052161b8924328528ef21fd85025b20221a374
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/BarChartWidget.php
@@ -0,0 +1,135 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Utility\ButtonUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete Bar Chart widget implementation
+ *
+ * Shows a widget with a bar chart. The data for this chart will be provided by the data provider you will set.
+ * You can add a button to the widget by defining a button provider.
+ *
+ * There are no options available for this widget
+ *
+ * @see ChartDataProviderInterface
+ * @see ButtonProviderInterface
+ */
+class BarChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface, RequireJsModuleInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+
+    /**
+     * @var ChartDataProviderInterface
+     */
+    private $dataProvider;
+
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+
+    /**
+     * @var ButtonProviderInterface|null
+     */
+    private $buttonProvider;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        ChartDataProviderInterface $dataProvider,
+        StandaloneView $view,
+        $buttonProvider = null,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->dataProvider = $dataProvider;
+        $this->view = $view;
+        $this->options = $options;
+        $this->buttonProvider = $buttonProvider;
+    }
+
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/ChartWidget');
+        $this->view->assignMultiple([
+            'button' => ButtonUtility::generateButtonConfig($this->buttonProvider),
+            'options' => $this->options,
+            'configuration' => $this->configuration,
+        ]);
+        return $this->view->render();
+    }
+
+    public function getEventData(): array
+    {
+        return [
+            'graphConfig' => [
+                'type' => 'bar',
+                'options' => [
+                    'maintainAspectRatio' => false,
+                    'legend' => [
+                        'display' => false,
+                    ],
+                    'scales' => [
+                        'yAxes' => [
+                            [
+                                'ticks' => [
+                                    'beginAtZero' => true,
+                                ],
+                            ],
+                        ],
+                        'xAxes' => [
+                            [
+                                'ticks' => [
+                                    'maxTicksLimit' => 15,
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+                'data' => $this->dataProvider->getChartData(),
+            ],
+        ];
+    }
+
+    public function getCssFiles(): array
+    {
+        return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css'];
+    }
+
+    public function getRequireJsModules(): array
+    {
+        return [
+            'TYPO3/CMS/Dashboard/Contrib/chartjs',
+            'TYPO3/CMS/Dashboard/ChartInitializer',
+        ];
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php b/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..aeb490d7894050de62303d2336718b15efe06f03
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/CtaWidget.php
@@ -0,0 +1,81 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Utility\ButtonUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete CTA button implementation
+ *
+ * Shows a widget with a CTA button to easily go to a specific page or do a specific action. You can add a button to the
+ * widget by defining a button provider.
+ *
+ * The following options are available during registration:
+ * - text           string          Adds a text to the widget to give some more background information about
+ *                                  what a user can expect when clicking the button. You can either enter a
+ *                                  normal string or a translation string (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text)
+ * @see ButtonProviderInterface
+ */
+class CtaWidget implements WidgetInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    /**
+     * @var ButtonProviderInterface|null
+     */
+    private $buttonProvider;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        StandaloneView $view,
+        $buttonProvider = null,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->view = $view;
+        $this->options =  array_merge(['text' => ''], $options);
+        $this->buttonProvider = $buttonProvider;
+    }
+
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/CtaWidget');
+        $this->view->assignMultiple([
+            'text' => $this->options['text'],
+            'options' => $this->options,
+            'button' => ButtonUtility::generateButtonConfig($this->buttonProvider),
+            'configuration' => $this->configuration
+        ]);
+        return $this->view->render();
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php
deleted file mode 100644
index 9f4b4190e8d3436d7d3e119e3a44508f27f58f2f..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationGettingStartedWidget.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * This widget will show a link to the documentation of TYPO3 to make it easier for people
- * to find the right documentation.
- */
-class DocumentationGettingStartedWidget extends AbstractCtaButtonWidget
-{
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.title';
-
-    /**
-     * @var string
-     */
-    protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.description';
-
-    /**
-     * @var string
-     */
-    protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.content.label';
-
-    /**
-     * @var string
-     */
-    protected $link = 'https://docs.typo3.org/m/typo3/tutorial-getting-started/master/en-us/Index.html';
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-text';
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php
deleted file mode 100644
index c3038d202462b7c08b8a545fcd1087162b472041..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTSconfigReferenceWidget.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * This widget will show a link to the documentation of TYPO3 to make it easier for people
- * to find the right documentation.
- */
-class DocumentationTSconfigReferenceWidget extends AbstractCtaButtonWidget
-{
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.title';
-
-    /**
-     * @var string
-     */
-    protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.text';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.description';
-
-    /**
-     * @var string
-     */
-    protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.content.label';
-
-    /**
-     * @var string
-     */
-    protected $link = 'https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/Index.html';
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-text';
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php b/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php
deleted file mode 100644
index b816da81b2e45b065f7f2de5a11c0d101f0fd411..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/DocumentationTypoScriptReference.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * This widget will show a link to the documentation of TYPO3 to make it easier for people
- * to find the right documentation.
- */
-class DocumentationTypoScriptReference extends AbstractCtaButtonWidget
-{
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.title';
-
-    /**
-     * @var string
-     */
-    protected $text = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.text';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.description';
-
-    /**
-     * @var string
-     */
-    protected $label = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.content.label';
-
-    /**
-     * @var string
-     */
-    protected $link = 'https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Index.html';
-
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-text';
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php b/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a2860302035641db93609d590cd8995b6c3d22f
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/DoughnutChartWidget.php
@@ -0,0 +1,121 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Utility\ButtonUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete Doughnut Chart widget implementation
+ *
+ * Shows a widget with a doughnut chart. The data for this chart will be provided by the data provider you will set.
+ * You can add a button to the widget by defining a button provider.
+ *
+ * There are no options available for this widget
+ *
+ * @see ChartDataProviderInterface
+ * @see ButtonProviderInterface
+ */
+class DoughnutChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface, RequireJsModuleInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+
+    /**
+     * @var ChartDataProviderInterface
+     */
+    private $dataProvider;
+
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+
+    /**
+     * @var ButtonProviderInterface|null
+     */
+    private $buttonProvider;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        ChartDataProviderInterface $dataProvider,
+        StandaloneView $view,
+        $buttonProvider = null,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->dataProvider = $dataProvider;
+        $this->view = $view;
+        $this->options = $options;
+        $this->buttonProvider = $buttonProvider;
+    }
+
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/ChartWidget');
+        $this->view->assignMultiple([
+            'button' => ButtonUtility::generateButtonConfig($this->buttonProvider),
+            'options' => $this->options,
+            'configuration' => $this->configuration,
+        ]);
+        return $this->view->render();
+    }
+
+    public function getEventData(): array
+    {
+        return [
+            'graphConfig' => [
+                'type' => 'doughnut',
+                'options' => [
+                    'maintainAspectRatio' => false,
+                    'legend' => [
+                        'display' => true,
+                        'position' => 'bottom'
+                    ],
+                    'cutoutPercentage' => 60
+                ],
+                'data' => $this->dataProvider->getChartData(),
+            ],
+        ];
+    }
+
+    public function getCssFiles(): array
+    {
+        return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css'];
+    }
+
+    public function getRequireJsModules(): array
+    {
+        return [
+            'TYPO3/CMS/Dashboard/Contrib/chartjs',
+            'TYPO3/CMS/Dashboard/ChartInitializer',
+        ];
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php
index 83d152c7bd606381a63419d48333c7df833ddc98..2e7e95c359ad4e46b94ec6078c186c85938e4908 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalCssInterface.php
@@ -2,8 +2,20 @@
 declare(strict_types = 1);
 namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
 
+/*
+ * 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!
+ */
+
 /**
- * Interface AdditionalCssInterface
  * In case a widget should provide additional CSS files, the widget must implement this interface.
  */
 interface AdditionalCssInterface
@@ -11,6 +23,7 @@ interface AdditionalCssInterface
     /**
      * This method returns an array with paths to required CSS files.
      * e.g. ['EXT:myext/Resources/Public/Css/my_widget.css']
+     *
      * @return array
      */
     public function getCssFiles(): array;
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php
index 6691a2bf56d4d2be01864f98922894fc42bcce4a..acb38d693233436d2bb8cd9087361a708f2a2b5f 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/AdditionalJavaScriptInterface.php
@@ -2,8 +2,20 @@
 declare(strict_types = 1);
 namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
 
+/*
+ * 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!
+ */
+
 /**
- * Interface AdditionalJavaScriptInterface
  * In case a widget should provide additional JavaScript files, the widget must implement this interface.
  */
 interface AdditionalJavaScriptInterface
@@ -11,6 +23,7 @@ interface AdditionalJavaScriptInterface
     /**
      * This method returns an array with paths to required JS files.
      * e.g. ['EXT:myext/Resources/Public/JavaScript/my_widget.js']
+     *
      * @return array
      */
     public function getJsFiles(): array;
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..a308a6ade61565cf596b245c1e994c7132c7ab8a
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ButtonProviderInterface.php
@@ -0,0 +1,45 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
+
+/*
+ * 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!
+ */
+
+/**
+ * In case a widget should have a button in the footer of the widget, this button must implement this interface.
+ */
+interface ButtonProviderInterface
+{
+    /**
+     * This method should return the title that will be shown as the text on the button. As the title will be
+     * translated within the template, you can also return a localization string like
+     * 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:button'
+     *
+     * @return string
+     */
+    public function getTitle(): string;
+
+    /**
+     * Return the link
+     *
+     * @return string
+     */
+    public function getLink(): string;
+
+    /**
+     * Specify the target of the link like '_blank'
+     *
+     * @return string
+     */
+    public function getTarget(): string;
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..78ee384ba8e8b749006e66b66948a3edbb091c41
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ChartDataProviderInterface.php
@@ -0,0 +1,34 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
+
+/*
+ * 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!
+ */
+
+/**
+ * Defines API for provider, used for chart widgets.
+ */
+interface ChartDataProviderInterface
+{
+    /**
+     * This method should provide the data for the graph.
+     * The data and options you have depend on the type of chart.
+     * More information can be found in the documentation of the specific type.
+     *
+     * @link https://www.chartjs.org/docs/latest/charts/bar.html#data-structure
+     * @link https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure
+     *
+     * @return array
+     */
+    public function getChartData(): array;
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php
index fae69e9c6d567955b3041639ef399d9f7f3b4694..b8ee4398c5b346d5b3e25be34fb6be483b170793 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/EventDataInterface.php
@@ -2,14 +2,27 @@
 declare(strict_types = 1);
 namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
 
+/*
+ * 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!
+ */
+
 /**
- * Interface EventDataInterface
  * In case a widget should provide additional data as JSON payload, the widget must implement this interface.
  */
 interface EventDataInterface
 {
     /**
      * This method returns data which should be send to the widget as JSON encoded value.
+     *
      * @return array
      */
     public function getEventData(): array;
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2899767c21ce3740ded77fd39b18a6711705438
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/ListDataProviderInterface.php
@@ -0,0 +1,30 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
+
+/*
+ * 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!
+ */
+
+/**
+ * The dataprovider of a ListWidget, should implement this interface
+ */
+interface ListDataProviderInterface
+{
+    /**
+     * Return the items to be shown. This should be an array like ['item 1', 'item 2', 'item 3']. This is a
+     * real simple list of items.
+     *
+     * @return array
+     */
+    public function getItems(): array;
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..21e6dbb4a6f1214776da0092c04294ee735b34ac
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/NumberWithIconDataProviderInterface.php
@@ -0,0 +1,29 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
+
+/*
+ * 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!
+ */
+
+/**
+ * The dataprovider of a NumberWithIcon widget should implement this interface
+ */
+interface NumberWithIconDataProviderInterface
+{
+    /**
+     * Return the number that should be shown in the widget
+     *
+     * @return int
+     */
+    public function getNumber(): int;
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php
index 7add78da29a705c97a6c3030b14bbfc24cf26727..71071bc4efcc55e33b5c917376fc953f38207b81 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/RequireJsModuleInterface.php
@@ -2,8 +2,20 @@
 declare(strict_types = 1);
 namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
 
+/*
+ * 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!
+ */
+
 /**
- * Interface RequireJsModuleInterface
  * In case a widget should provide additional requireJS modules, the widget must implement this interface.
  */
 interface RequireJsModuleInterface
@@ -14,6 +26,7 @@ interface RequireJsModuleInterface
      *   'TYPO3/CMS/Backend/Modal',
      *   'TYPO3/CMS/MyExt/FooBar' => 'function(FooBar) { ... }'
      * ]
+     *
      * @return array
      */
     public function getRequireJsModules(): array;
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..37e0964f56eb5229e4014ca7967a4cedcfde85e1
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetConfigurationInterface.php
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
+
+/*
+ * 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!
+ */
+
+/**
+ * Defines API of configuration for a widget.
+ * This is separated by concrete implementation of a widget (WidgetInterface).
+ * The configuration is used to generate UX, and other stuff.
+ */
+interface WidgetConfigurationInterface
+{
+    /**
+     * Returns the unique identifer of a widget
+     *
+     * @return string
+     */
+    public function getIdentifier(): string;
+
+    /**
+     * Returns the service name providing the widget implementation
+     *
+     * @return string
+     */
+    public function getServiceName(): string;
+
+    /**
+     * Returns array of group names associated to this widget
+     *
+     * @return array
+     */
+    public function getGroupNames(): array;
+
+    /**
+     * Returns the title of a widget, this is used for the widget selector
+     *
+     * @return string
+     */
+    public function getTitle(): string;
+
+    /**
+     * Returns the description of a widget, this is used for the widget selector
+     *
+     * @return string
+     */
+    public function getDescription(): string;
+
+    /**
+     * Returns the icon identifier of a widget, this is used for the widget selector
+     *
+     * @return string
+     */
+    public function getIconIdentifier(): string;
+
+    /**
+     * Returns the height of a widget (small, medium, large)
+     *
+     * @return string
+     */
+    public function getHeight(): string;
+
+    /**
+     * Returns the width of a widget (small, medium, large)
+     *
+     * @return string
+     */
+    public function getWidth(): string;
+
+    /**
+     * This method returns additional CSS classes which should be added to the rendered widget
+     *
+     * @return string
+     */
+    public function getAdditionalCssClasses(): string;
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php
index b122bb81d20794ab4e59c3027c85c8a40a75b354..c59c6521d801719d53bf5fb28e916b7d0662e8f0 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Interfaces/WidgetInterface.php
@@ -2,6 +2,19 @@
 declare(strict_types = 1);
 namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
 
+/*
+ * 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!
+ */
+
 /**
  * The WidgetInterface is the base interface for all kind of widgets.
  * All widgets must implement this interface.
@@ -9,54 +22,12 @@ namespace TYPO3\CMS\Dashboard\Widgets\Interfaces;
  */
 interface WidgetInterface
 {
-    /**
-     * Returns the unique identifer of a widget
-     *
-     * @return string
-     */
-    public function getIdentifier(): string;
-
-    /**
-     * Returns the title of a widget, this is used for the widget selector
-     * @return string
-     */
-    public function getTitle(): string;
-
-    /**
-     * Returns the description of a widget, this is used for the widget selector
-     * @return string
-     */
-    public function getDescription(): string;
-
-    /**
-     * Returns the icon identifier of a widget, this is used for the widget selector
-     * @return string
-     */
-    public function getIconIdentifier(): string;
-
-    /**
-     * Returns the height of a widget in rows (1-6)
-     * @return int
-     */
-    public function getHeight(): int;
-
-    /**
-     * Returns the width of a widget in columns (1-4)
-     * @return int
-     */
-    public function getWidth(): int;
-
     /**
      * This method returns the content of a widget. The returned markup will be delivered
      * by an AJAX call and will not be escaped.
      * Be aware of XSS and ensure that the content is well encoded.
+     *
      * @return string
      */
     public function renderWidgetContent(): string;
-
-    /**
-     * This method returns additional CSS classes which should be added to the rendered widget
-     * @return string
-     */
-    public function getAdditionalClasses(): string;
 }
diff --git a/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php b/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ca2ea49f8e3005d322b20b62fef0e7739c627b0
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/ListWidget.php
@@ -0,0 +1,106 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ListDataProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete List Widget implementation
+ *
+ * The widget will show a simple list with items provided by a data provider. You can add a button to the widget by
+ * defining a button provider.
+ *
+ * There are no options available for this widget
+ *
+ * @see ListDataProviderInterface
+ * @see ButtonProviderInterface
+ */
+class ListWidget implements WidgetInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+
+    /**
+     * @var array
+     */
+    private $options;
+    /**
+     * @var ButtonProviderInterface|null
+     */
+    private $buttonProvider;
+
+    /**
+     * @var ListDataProviderInterface
+     */
+    private $dataProvider;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        ListDataProviderInterface $dataProvider,
+        StandaloneView $view,
+        $buttonProvider = null,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->view = $view;
+        $this->options = $options;
+        $this->buttonProvider = $buttonProvider;
+        $this->dataProvider = $dataProvider;
+    }
+
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/ListWidget');
+        $this->view->assignMultiple([
+            'items' => $this->getItems(),
+            'options' => $this->options,
+            'button' => $this->getButton(),
+            'configuration' => $this->configuration,
+        ]);
+        return $this->view->render();
+    }
+
+    protected function getItems(): array
+    {
+        return $this->dataProvider->getItems();
+    }
+
+    private function getButton(): array
+    {
+        $button = [];
+
+        if ($this->buttonProvider instanceof ButtonProviderInterface && $this->buttonProvider->getTitle() && $this->buttonProvider->getLink()) {
+            $button = [
+                'text' => $this->buttonProvider->getTitle(),
+                'link' => $this->buttonProvider->getLink(),
+                'target' => $this->buttonProvider->getTarget(),
+            ];
+        }
+
+        return $button;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php b/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..41446748d217df98fb39a94a12a19a296ce2e35b
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/NumberWithIconWidget.php
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\NumberWithIconDataProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete Number with Icon implementation
+ *
+ * The widget will show widget with an icon, a number, a title and a subtitle. The number is provided by a data
+ * provider.
+ *
+ * The following options are available during registration:
+ * - icon           string        The icon-identifier of the icon that should be shown in the widget. You should
+ *                                register your icon with the Icon API
+ * - title          string        The main title that will be shown in the widget as an explanation of the shown number.
+ *                                You can either enter a normal string or a translation string
+ *                                (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title)
+ * - subtitle       string        The subtitle that will give some additional information about the number and title.
+ *                                You can either enter a normal string or a translation string
+ *                                (eg. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle)
+ *
+ * @see NumberWithIconDataProviderInterface
+ */
+class NumberWithIconWidget implements WidgetInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+    /**
+     * @var array
+     */
+    private $options;
+    /**
+     * @var NumberWithIconDataProviderInterface
+     */
+    private $dataProvider;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        NumberWithIconDataProviderInterface $dataProvider,
+        StandaloneView $view,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->view = $view;
+        $this->options = $options;
+        $this->dataProvider = $dataProvider;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/NumberWithIconWidget');
+        $this->view->assignMultiple([
+            'icon' => $this->options['icon'],
+            'title' => $this->options['title'],
+            'subtitle' => $this->options['subtitle'],
+            'number' => $this->dataProvider->getNumber(),
+            'options' => $this->options,
+            'configuration' => $this->configuration
+        ]);
+        return $this->view->render();
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a8a9d812489c80b90067fd95adaad5886bec25e
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/ButtonProvider.php
@@ -0,0 +1,63 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Provider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+
+/**
+ * Provide link for sys log button.
+ * Check whether belog is enabled and add link to module.
+ * No link is returned if not enabled.
+ */
+class ButtonProvider implements ButtonProviderInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+
+    /**
+     * @var string
+     */
+    private $target;
+
+    /**
+     * @var string
+     */
+    private $link;
+
+    public function __construct(string $title, string $link, string $target = '')
+    {
+        $this->title = $title;
+        $this->target = $target;
+        $this->link = $link;
+    }
+
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    public function getLink(): string
+    {
+        return $this->link;
+    }
+
+    public function getTarget(): string
+    {
+        return $this->target;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php
similarity index 66%
rename from typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php
rename to typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php
index 3ed002e9bf0b6a705bd13f54c3a18707cea98f51..159c52b03a2d7ffc6c863c6431d0a66ad58c854d 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/FailedLoginsWidget.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/NumberOfFailedLoginsDataProvider.php
@@ -1,6 +1,6 @@
 <?php
 declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
+namespace TYPO3\CMS\Dashboard\Widgets\Provider;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -21,39 +21,11 @@ use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
 use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
 use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\NumberWithIconDataProviderInterface;
 
-/**
- * This widget will show the number of failed logins during a given period
- */
-class FailedLoginsWidget extends AbstractNumberWithIconWidget
+class NumberOfFailedLoginsDataProvider implements NumberWithIconDataProviderInterface
 {
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description';
-
-    /**
-     * @var string
-     */
-    protected $subtitle = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle';
-
-    /**
-     * @var string
-     */
-    protected $icon = 'content-elements-login';
-
-    protected function initializeView(): void
-    {
-        $this->number = $this->getNumberOfFailedLogins();
-        parent::initializeView();
-    }
-
-    public function getNumberOfFailedLogins(int $secondsBack = 86400): int
+    public function getNumber(int $secondsBack = 86400): int
     {
         $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
         $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
diff --git a/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..6be6af5c5a9d4fd2f57b3c4991391f71f4432498
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogButtonProvider.php
@@ -0,0 +1,68 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets\Provider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+
+/**
+ * Provide link for sys log button.
+ * Check whether belog is enabled and add link to module.
+ * No link is returned if not enabled.
+ */
+class SysLogButtonProvider implements ButtonProviderInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+
+    /**
+     * @var string
+     */
+    private $target;
+
+    public function __construct(string $title, string $target = '')
+    {
+        $this->title = $title;
+        $this->target = $target;
+    }
+
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    public function getLink(): string
+    {
+        if (ExtensionManagementUtility::isLoaded('belog')) {
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            return (string)$uriBuilder->buildUriFromRoute(
+                'system_BelogLog',
+                ['tx_belog_system_beloglog[constraint][action]' => -1]
+            );
+        }
+
+        return '';
+    }
+
+    public function getTarget(): string
+    {
+        return $this->target;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php
similarity index 61%
rename from typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php
rename to typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php
index 96f16eedcaa216740d7938df34d9f28f88be4530..0a6b70c0dd96138581d18bd73e77ce73adf5183e 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/SysLogErrorsWidget.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/SysLogErrorsDataProvider.php
@@ -1,6 +1,6 @@
 <?php
 declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
+namespace TYPO3\CMS\Dashboard\Widgets\Provider;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -15,78 +15,54 @@ namespace TYPO3\CMS\Dashboard\Widgets;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Dashboard\WidgetApi;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface;
 
 /**
- * This widget will show the number of system log errors in a bar chart
+ * Provides chart data for sys log errors.
  */
-class SysLogErrorsWidget extends AbstractBarChartWidget
+class SysLogErrorsDataProvider implements ChartDataProviderInterface
 {
     /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description';
-
-    /**
-     * @var string
-     */
-    protected $buttonText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText';
-
-    /**
+     * Number of days to gather information for.
+     *
      * @var int
      */
-    protected $width = 4;
+    protected $days = 31;
 
     /**
-     * @var int
+     * @var array
      */
-    protected $height = 4;
+    protected $labels = [];
 
     /**
-     * @var mixed[]
+     * @var array
      */
     protected $data = [];
 
-    /**
-     * @var mixed[]
-     */
-    protected $labels = [];
-
-    protected function initializeView(): void
+    public function __construct(int $days = 31)
     {
-        if (ExtensionManagementUtility::isLoaded('belog')) {
-            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->buttonLink = (string)$uriBuilder->buildUriFromRoute(
-                'system_BelogLog',
-                ['tx_belog_system_beloglog[constraint][action]' => -1]
-            );
-        }
-
-        parent::initializeView();
+        $this->days = $days;
     }
+
     /**
      * @inheritDoc
      */
-    protected function prepareChartData(): void
+    public function getChartData(): array
     {
-        $this->calculateDataForLastDays(31);
+        $this->calculateDataForLastDays();
 
-        $this->chartData = [
+        return [
             'labels' => $this->labels,
             'datasets' => [
                 [
                     'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'),
-                    'backgroundColor' => $this->chartColors[0],
+                    'backgroundColor' => WidgetApi::getDefaultChartColors()[0],
                     'border' => 0,
                     'data' => $this->data
                 ]
@@ -118,11 +94,11 @@ class SysLogErrorsWidget extends AbstractBarChartWidget
             ->fetchColumn();
     }
 
-    protected function calculateDataForLastDays(int $days): void
+    protected function calculateDataForLastDays(): void
     {
         $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d';
 
-        for ($daysBefore=$days; $daysBefore>=0; $daysBefore--) {
+        for ($daysBefore = $this->days; $daysBefore >= 0; $daysBefore--) {
             $this->labels[] = date($format, strtotime('-' . $daysBefore . ' day'));
             $startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00');
             $endPeriod =  strtotime('-' . $daysBefore . ' day 23:59:59');
@@ -130,4 +106,9 @@ class SysLogErrorsWidget extends AbstractBarChartWidget
             $this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod);
         }
     }
+
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
 }
diff --git a/typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php b/typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php
similarity index 61%
rename from typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php
rename to typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php
index 4b627d4649b9b4c552effb13173f9397890e3c6b..06d330b7f1f8bcd45ce1f9939f6c99a1b92583e1 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/TypeOfUsersWidget.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/Provider/TypeOfUsersChartDataProvider.php
@@ -1,6 +1,6 @@
 <?php
 declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
+namespace TYPO3\CMS\Dashboard\Widgets\Provider;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -17,39 +17,39 @@ namespace TYPO3\CMS\Dashboard\Widgets;
 
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Dashboard\WidgetApi;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ChartDataProviderInterface;
 
-/**
- * This widget will show the type of users (admin / non-admin) in a doughnut chart
- */
-class TypeOfUsersWidget extends AbstractDoughnutChartWidget
+class TypeOfUsersChartDataProvider implements ChartDataProviderInterface
 {
     /**
-     * @var string
+     * @var LanguageService
      */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title';
+    private $languageService;
 
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description';
+    public function __construct(LanguageService $languageService)
+    {
+        $this->languageService = $languageService;
+    }
 
     /**
      * @inheritDoc
      */
-    protected function prepareChartData(): void
+    public function getChartData(): array
     {
         $adminUsers = $this->getNumberOfUsers(true);
         $normalUsers = $this->getNumberOfUsers(false);
 
-        $this->chartData = [
+        return [
             'labels' => [
-                $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'),
-                $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers')
+                $this->languageService ->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'),
+                $this->languageService ->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers')
             ],
             'datasets' => [
                 [
-                    'backgroundColor' => [$this->chartColors[0], $this->chartColors[1]],
+                    'backgroundColor' => WidgetApi::getDefaultChartColors(),
                     'data' => [$normalUsers, $adminUsers]
                 ]
             ],
diff --git a/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php b/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..14ba8673d04dc094169a468fe60b23afafa07f3a
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/RssWidget.php
@@ -0,0 +1,144 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface as Cache;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\ButtonProviderInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Concrete RSS widget implementation
+ *
+ * The widget will show a certain number of items of the given RSS feed. The feed will be set by the feedUrl option. You
+ * can add a button to the widget by defining a button provider.
+ *
+ * The following options are available during registration:
+ * - feedUrl        string                      Defines the URL or file providing the RSS Feed.
+ *                                              This is read by the widget in order to fetch entries to show.
+ * - limit          int     default: 5          Defines how many RSS items should be shown.
+ * - lifetime       int     default: 43200      Defines how long to wait, in seconds, until fetching RSS Feed again
+ *
+ * @see ButtonProviderInterface
+ */
+class RssWidget implements WidgetInterface
+{
+    /**
+     * @var WidgetConfigurationInterface
+     */
+    private $configuration;
+
+    /**
+     * @var StandaloneView
+     */
+    private $view;
+
+    /**
+     * @var Cache
+     */
+    private $cache;
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    /**
+     * @var ButtonProviderInterface|null
+     */
+    private $buttonProvider;
+
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        Cache $cache,
+        StandaloneView $view,
+        $buttonProvider = null,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->view = $view;
+        $this->cache = $cache;
+        $this->options = array_merge(
+            [
+            'limit' => 5,
+            'lifeTime' => 43200
+            ],
+            $options
+        );
+        $this->buttonProvider = $buttonProvider;
+    }
+
+    public function renderWidgetContent(): string
+    {
+        $this->view->setTemplate('Widget/RssWidget');
+        $this->view->assignMultiple([
+            'items' => $this->getRssItems(),
+            'options' => $this->options,
+            'button' => $this->getButton(),
+            'configuration' => $this->configuration,
+        ]);
+        return $this->view->render();
+    }
+
+    protected function getRssItems(): array
+    {
+        $cacheHash = md5($this->options['feedUrl']);
+        if ($items = $this->cache->get($cacheHash)) {
+            return $items;
+        }
+
+        $rssContent = GeneralUtility::getUrl($this->options['feedUrl']);
+        if ($rssContent === false) {
+            throw new \RuntimeException('RSS URL could not be fetched', 1573385431);
+        }
+        $rssFeed = simplexml_load_string($rssContent);
+        $itemCount = 0;
+        $items = [];
+        foreach ($rssFeed->channel->item as $item) {
+            if ($itemCount >= $this->options['limit']) {
+                break;
+            }
+
+            $items[] = [
+                'title' => (string)$item->title,
+                'link' => trim((string)$item->link),
+                'pubDate' => (string)$item->pubDate,
+                'description' => (string)$item->description,
+            ];
+            $itemCount++;
+        }
+        $this->cache->set($cacheHash, $items, ['dashboard_rss'], $this->options['lifeTime']);
+
+        return $items;
+    }
+
+    private function getButton(): array
+    {
+        $button = [];
+
+        if ($this->buttonProvider instanceof ButtonProviderInterface && $this->buttonProvider->getTitle() && $this->buttonProvider->getLink()) {
+            $button = [
+                'text' => $this->buttonProvider->getTitle(),
+                'link' => $this->buttonProvider->getLink(),
+                'target' => $this->buttonProvider->getTarget(),
+            ];
+        }
+
+        return $button;
+    }
+}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php b/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php
similarity index 54%
rename from typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php
rename to typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php
index 2dd249edf553b1ce027c2749032ebb5fe4b0cb1f..6d31f3eb0e45d27109fe038a582ad4e76d7af3a6 100644
--- a/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformation.php
+++ b/typo3/sysext/dashboard/Classes/Widgets/T3GeneralInformationWidget.php
@@ -17,53 +17,58 @@ namespace TYPO3\CMS\Dashboard\Widgets;
 
 use TYPO3\CMS\Core\Information\Typo3Information;
 use TYPO3\CMS\Core\Information\Typo3Version;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetInterface;
+use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
- * This widget will show general information regarding TYPO3
+ * Concrete TYPO3 information widget
+ *
+ * This widget will give some general information about TYPO3 version and the version installed.
+ *
+ * There are no options available for this widget
  */
-class T3GeneralInformation extends AbstractWidget
+class T3GeneralInformationWidget implements WidgetInterface
 {
     /**
-     * @var string
+     * @var WidgetConfigurationInterface
      */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.title';
+    private $configuration;
 
     /**
-     * @var string
+     * @var StandaloneView
      */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.description';
+    private $view;
 
     /**
-     * @var string
+     * @var array
      */
-    protected $templateName = 'T3GeneralInformation';
+    private $options;
 
-    /**
-     * @var string
-     */
-    protected $iconIdentifier = 'content-widget-text';
-
-    /**
-     * @var int
-     */
-    protected $height = 4;
-
-    /**
-     * @var int
-     */
-    protected $width = 4;
+    public function __construct(
+        WidgetConfigurationInterface $configuration,
+        StandaloneView $view,
+        array $options = []
+    ) {
+        $this->configuration = $configuration;
+        $this->view = $view;
+        $this->options = $options;
+    }
 
     public function renderWidgetContent(): string
     {
         $typo3Information = new Typo3Information();
         $typo3Version = new Typo3Version();
+
+        $this->view->setTemplate('Widget/T3GeneralInformationWidget');
         $this->view->assignMultiple([
             'title' => 'TYPO3 CMS ' . $typo3Version->getVersion(),
             'copyrightYear' => $typo3Information->getCopyrightYear(),
             'currentVersion' => $typo3Version->getVersion(),
             'donationUrl' => $typo3Information::URL_DONATE,
             'copyRightNotice' => $typo3Information->getCopyrightNotice(),
-
+            'options' => $this->options,
+            'configuration' => $this->configuration
         ]);
         return $this->view->render();
     }
diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php b/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php
deleted file mode 100644
index 1ddbb57f75cbb9f28fac570a95ce86ef0e851332..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/T3NewsWidget.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * This widget will show the latest news from the TYPO3 news RSS feed right
- * on the dashboard.
- */
-class T3NewsWidget extends AbstractRssWidget
-{
-    /**
-     * @var string
-     */
-    protected $rssFile = 'https://www.typo3.org/rss';
-
-    /**
-     * @var int
-     */
-    protected $lifeTime = 43200; // 12 hours cache
-
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description';
-
-    /**
-     * @var string
-     */
-    protected $moreItemsLink = 'https://typo3.org/project/news';
-
-    /**
-     * @var string
-     */
-    protected $moreItemsText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems';
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php b/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php
deleted file mode 100644
index a33fa35cbb2f930a3384a68ce0d8baa0ee567bdb..0000000000000000000000000000000000000000
--- a/typo3/sysext/dashboard/Classes/Widgets/T3SecurityAdvisoriesWidget.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Dashboard\Widgets;
-
-/*
- * 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!
- */
-
-/**
- * This widget will show the latest security advisories from the TYPO3 news RSS feed
- * right on the dashboard.
- */
-class T3SecurityAdvisoriesWidget extends AbstractRssWidget
-{
-    /**
-     * @var string
-     */
-    protected $rssFile = 'https://typo3.org/?type=101';
-
-    /**
-     * @var int
-     */
-    protected $lifeTime = 43200; // 12 hours cache
-
-    /**
-     * @var string
-     */
-    protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.title';
-
-    /**
-     * @var string
-     */
-    protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.description';
-
-    /**
-     * @var string
-     */
-    protected $moreItemsLink = 'https://typo3.org/help/security-advisories';
-
-    /**
-     * @var string
-     */
-    protected $moreItemsText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.moreItems';
-}
diff --git a/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php b/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php
new file mode 100644
index 0000000000000000000000000000000000000000..008ca099cc77e3f1384a9c4277c5e2d4ffe11ed6
--- /dev/null
+++ b/typo3/sysext/dashboard/Classes/Widgets/WidgetConfiguration.php
@@ -0,0 +1,144 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Dashboard\Widgets;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
+
+class WidgetConfiguration implements WidgetConfigurationInterface
+{
+    /**
+     * @var string
+     */
+    private $identifier;
+
+    /**
+     * @var string
+     */
+    private $serviceName;
+
+    /**
+     * @var array
+     */
+    private $groupNames;
+
+    /**
+     * @var string
+     */
+    private $title;
+
+    /**
+     * @var string
+     */
+    private $description;
+
+    /**
+     * @var string
+     */
+    private $iconIdentifier;
+
+    /**
+     * @var string
+     */
+    private $height;
+
+    /**
+     * @var string
+     */
+    private $width;
+
+    /**
+     * @var array
+     */
+    private $additionalCssClasses;
+
+    /**
+     * @throws \InvalidArgumentException If non valid parameters were provided.
+     */
+    public function __construct(
+        string $identifier,
+        string $serviceName,
+        array $groupNames,
+        string $title,
+        string $description,
+        string $iconIdentifier,
+        string $height,
+        string $width,
+        array $additionalCssClasses
+    ) {
+        $allowedSizes = ['small', 'medium', 'large'];
+        if (!in_array($height, $allowedSizes, true)) {
+            throw new \InvalidArgumentException('Height of widgets has to be small, medium or large', 1584778196);
+        }
+        if (!in_array($height, $allowedSizes, true)) {
+            throw new \InvalidArgumentException('Width of widgets has to be small, medium or large', 1585249769);
+        }
+
+        $this->identifier = $identifier;
+        $this->serviceName = $serviceName;
+        $this->groupNames = $groupNames;
+        $this->title = $title;
+        $this->description = $description;
+        $this->iconIdentifier = $iconIdentifier;
+        $this->height = $height;
+        $this->width = $width;
+        $this->additionalCssClasses = $additionalCssClasses;
+    }
+
+    public function getIdentifier(): string
+    {
+        return $this->identifier;
+    }
+
+    public function getServiceName(): string
+    {
+        return $this->serviceName;
+    }
+
+    public function getGroupNames(): array
+    {
+        return $this->groupNames;
+    }
+
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    public function getDescription(): string
+    {
+        return $this->description;
+    }
+
+    public function getIconIdentifier(): string
+    {
+        return $this->iconIdentifier;
+    }
+
+    public function getHeight(): string
+    {
+        return $this->height;
+    }
+
+    public function getWidth(): string
+    {
+        return $this->width;
+    }
+
+    public function getAdditionalCssClasses(): string
+    {
+        return implode(' ', $this->additionalCssClasses);
+    }
+}
diff --git a/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml b/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5744d3be9948b5be1d50210a5f0703ed96c3c5bb
--- /dev/null
+++ b/typo3/sysext/dashboard/Configuration/Backend/DashboardWidgets.yaml
@@ -0,0 +1,208 @@
+services:
+  _defaults:
+    autowire: true
+    autoconfigure: true
+    public: false
+
+  TYPO3\CMS\Dashboard\Widgets\:
+    resource: '../Classes/Widgets/*'
+
+  TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration:
+    autowire: false
+
+  TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider:
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText'
+
+  cache.dashboard.rss:
+    class: 'TYPO3\CMS\Core\Cache\Frontend\FrontendInterface'
+    factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
+    arguments:
+      $identifier: 'dashboard_rss'
+
+  dashboard.buttons.t3news:
+    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems'
+      $link: 'https://typo3.org/project/news'
+      $target: '_blank'
+
+  dashboard.buttons.t3securityAdvisories:
+    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.moreItems'
+      $link: 'https://typo3.org/help/security-advisories'
+      $target: '_blank'
+
+  dashboard.buttons.docGettingStarted:
+    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.content.label'
+      $link: 'https://docs.typo3.org/m/typo3/tutorial-getting-started/master/en-us/Index.html'
+      $target: '_blank'
+
+  dashboard.buttons.docTypoScriptReference:
+    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.content.label'
+      $link: 'https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Index.html'
+      $target: '_blank'
+
+  dashboard.buttons.docTSconfig:
+    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
+    arguments:
+      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.content.label'
+      $link: 'https://docs.typo3.org/m/typo3/reference-tsconfig/master/en-us/Index.html'
+      $target: '_blank'
+
+  dashboard.views.widget:
+    class: 'TYPO3\CMS\Fluid\View\StandaloneView'
+    public: true
+    factory: ['TYPO3\CMS\Dashboard\Views\Factory', 'widgetTemplate']
+
+  dashboard.widget.t3news:
+    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $cache: '@cache.dashboard.rss'
+      $buttonProvider: '@dashboard.buttons.t3news'
+      $options:
+        feedUrl: 'https://www.typo3.org/rss'
+    tags:
+      - name: dashboard.widget
+        identifier: 't3news'
+        groupNames: 'typo3'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
+        iconIdentifier: 'content-widget-rss'
+        height: 'large'
+        width: 'medium'
+
+  dashboard.widget.sysLogErrors:
+    class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget'
+    arguments:
+      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider'
+      $view: '@dashboard.views.widget'
+      $buttonProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider'
+    tags:
+      - name: dashboard.widget
+        identifier: 'sysLogErrors'
+        groupNames: 'systemInfo'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description'
+        iconIdentifier: 'content-widget-chart-bar'
+        additionalCssClasses: 'dashboard-item--chart'
+        height: 'medium'
+        width: 'medium'
+
+  dashboard.widget.docGettingStarted:
+    class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $buttonProvider: '@dashboard.buttons.docGettingStarted'
+      $options:
+        text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text'
+    tags:
+      - name: dashboard.widget
+        identifier: 'docGettingStarted'
+        groupNames: 'documentation'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.description'
+        iconIdentifier: 'content-widget-text'
+        height: 'small'
+
+  dashboard.widget.docTypoScriptReference:
+    class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $buttonProvider: '@dashboard.buttons.docTypoScriptReference'
+      $options:
+        text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.text'
+    tags:
+      - name: dashboard.widget
+        identifier: 'docTypoScriptReference'
+        groupNames: 'documentation'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.typoscriptReference.description'
+        iconIdentifier: 'content-widget-text'
+        height: 'small'
+
+  dashboard.widget.docTSconfig:
+    class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $buttonProvider: '@dashboard.buttons.docTSconfig'
+      $options:
+        text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.text'
+    tags:
+      - name: dashboard.widget
+        identifier: 'docTSconfig'
+        groupNames: 'documentation'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.TSconfigReference.description'
+        iconIdentifier: 'content-widget-text'
+        height: 'small'
+
+  dashboard.widget.t3information:
+    class: 'TYPO3\CMS\Dashboard\Widgets\T3GeneralInformationWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+    tags:
+      - name: dashboard.widget
+        identifier: 't3information'
+        groupNames: 'general'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3information.description'
+        iconIdentifier: 'content-widget-text'
+        height: 'medium'
+        width: 'medium'
+
+  dashboard.widget.typeOfUsers:
+    class: 'TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\TypeOfUsersChartDataProvider'
+    tags:
+      - name: dashboard.widget
+        identifier: 'typeOfUsers'
+        groupNames: 'systemInfo'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description'
+        iconIdentifier: 'content-widget-chart-pie'
+        additionalCssClasses: 'dashboard-item--chart'
+        height: 'large'
+
+  dashboard.widget.t3securityAdvisories:
+    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
+    arguments:
+      $view: '@dashboard.views.widget'
+      $cache: '@cache.dashboard.rss'
+      $buttonProvider: '@dashboard.buttons.t3securityAdvisories'
+      $options:
+        feedUrl: 'https://typo3.org/?type=101'
+    tags:
+      - name: dashboard.widget
+        identifier: 't3securityAdvisories'
+        groupNames: 'typo3'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3securityAdvisories.description'
+        iconIdentifier: 'content-widget-rss'
+        height: 'large'
+        width: 'medium'
+
+  dashboard.widget.failedLogins:
+    class: 'TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget'
+    arguments:
+      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\NumberOfFailedLoginsDataProvider'
+      $view: '@dashboard.views.widget'
+      $options:
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title'
+        subtitle: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle'
+        icon: 'content-elements-login'
+    tags:
+      - name: dashboard.widget
+        identifier: 'failedLogins'
+        groupNames: 'systemInfo'
+        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title'
+        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description'
+        iconIdentifier: 'content-widget-number'
diff --git a/typo3/sysext/dashboard/Configuration/Services.yaml b/typo3/sysext/dashboard/Configuration/Services.yaml
index 711d9111cb5dd45e68f59b9b8cfe5db1490aef16..cebad0f0d6f90b2103e739a9d31e054f8c47d8fc 100644
--- a/typo3/sysext/dashboard/Configuration/Services.yaml
+++ b/typo3/sysext/dashboard/Configuration/Services.yaml
@@ -1,3 +1,6 @@
+imports:
+  - { resource: Backend/DashboardWidgets.yaml }
+
 services:
   _defaults:
     autowire: true
@@ -6,6 +9,7 @@ services:
 
   TYPO3\CMS\Dashboard\:
     resource: '../Classes/*'
+    exclude: '../Classes/Widgets/*'
 
   TYPO3\CMS\Dashboard\Controller\DashboardController:
     public: true
@@ -15,66 +19,3 @@ services:
 
   TYPO3\CMS\Dashboard\DasboardRegistry:
     public: true
-
-  TYPO3\CMS\Dashboard\Widgets\DocumentationGettingStartedWidget:
-    arguments: ['docGettingStarted']
-    tags:
-      - name: dashboard.widget
-        identifier: docGettingStarted
-        widgetGroups: 'documentation'
-
-  TYPO3\CMS\Dashboard\Widgets\DocumentationTypoScriptReference:
-    arguments: ['docTypoScriptReference']
-    tags:
-      - name: dashboard.widget
-        identifier: docTypoScriptReference
-        widgetGroups: 'documentation'
-
-  TYPO3\CMS\Dashboard\Widgets\DocumentationTSconfigReferenceWidget:
-    arguments: ['docTSconfig']
-    tags:
-      - name: dashboard.widget
-        identifier: docTSconfig
-        widgetGroups: 'documentation'
-
-  TYPO3\CMS\Dashboard\Widgets\T3GeneralInformation:
-    arguments: ['t3information']
-    tags:
-      - name: dashboard.widget
-        identifier: t3information
-        widgetGroups: 'general'
-
-  TYPO3\CMS\Dashboard\Widgets\SysLogErrorsWidget:
-    arguments: ['sysLogErrors']
-    tags:
-      - name: dashboard.widget
-        identifier: sysLogErrors
-        widgetGroups: 'systemInfo'
-
-  TYPO3\CMS\Dashboard\Widgets\TypeOfUsersWidget:
-    arguments: ['typeOfUsers']
-    tags:
-      - name: dashboard.widget
-        identifier: typeOfUsers
-        widgetGroups: 'systemInfo'
-
-  TYPO3\CMS\Dashboard\Widgets\FailedLoginsWidget:
-    arguments: ['failedLogins']
-    tags:
-      - name: dashboard.widget
-        identifier: failedLogins
-        widgetGroups: 'general'
-
-  TYPO3\CMS\Dashboard\Widgets\T3NewsWidget:
-    arguments: ['t3news']
-    tags:
-      - name: dashboard.widget
-        identifier: t3news
-        widgetGroups: 'typo3'
-
-  TYPO3\CMS\Dashboard\Widgets\T3SecurityAdvisoriesWidget:
-    arguments: ['t3securityAdvisories']
-    tags:
-      - name: dashboard.widget
-        identifier: t3securityAdvisories
-        widgetGroups: 'typo3'
diff --git a/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html b/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html
index d18e1f94d01254bc50e98b51ddda45d0b3c7acec..4af5719f7e748d300eec798e9c8995f32ed75514 100644
--- a/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Layouts/Widget/Widget.html
@@ -1,5 +1,5 @@
 <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
-    <div class="widget-content-title"><span>{title}</span></div>
+    <div class="widget-content-title"><span>{f:translate(id: configuration.title, default: configuration.title)}</span></div>
     <div class="widget-content-main">
         <f:render section="main" optional="true" />
     </div>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html b/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html
index 27c80a43bcac625fe88580bfa99aa68cc11503fb..b5f2ed8e133fb76a8d1f2389331fddc51058fb18 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Dashboard/Main.html
@@ -27,7 +27,7 @@
     <f:then>
         <div class="dashboard-grid">
             <f:for each="{currentDashboard.widgets}" as="widget" iteration="widgetIterator" key="widgetHash">
-                <div class="dashboard-item dashboard-item--w{widget.width} dashboard-item--h{widget.height} {widget.additionalClasses} dashboard-item--enableSelect" data-widget-hash="{widgetHash}" data-widget-key="{widget.identifier}">
+                <div class="dashboard-item dashboard-item--w-{widget.width} dashboard-item--h-{widget.height} {widget.additionalCssClasses} dashboard-item--enableSelect" data-widget-hash="{widgetHash}" data-widget-key="{widget.identifier}">
                     <div class="dashboard-item-content">
                         <div class="widget widget-identifier-{widget.identifier}">
                             <div class="widget-waiting">
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html
index 4e753d334fe82f10c5a8030ba7699174d06b2d1b..45b2fbe4d322b6d1aee742a4fc5cbb3b3844a101 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ChartWidget.html
@@ -10,7 +10,7 @@
 <f:section name="footer">
 
     <f:if condition="{button}">
-        <a href="{button.link}" target="{button.target}" class="widget-cta">{button.text}</a>
+        <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a>
     </f:if>
 
 </f:section>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html
index 5f86147458f4bcce69d7ae8a418eed4be96b5d4e..97f590fa0c05b09e9845182eaacbe67d352bd6cc 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/CtaWidget.html
@@ -3,24 +3,13 @@
 <f:section name="main">
 
     <f:if condition="{text}">
-        {text}
+        <f:translate id='{text}' default='{text}' />
     </f:if>
 
 </f:section>
 <f:section name="footer">
-
-    <f:if condition="{link}">
-        <f:link.external uri="{link}" target="_blank" class="widget-cta">
-            <f:if condition="{icon}">
-                <div class="widget-cta-icon">
-                    <core:icon identifier="{icon}" size="large" alternativeMarkupIdentifier="inline" />
-                </div>
-            </f:if>
-            <f:if condition="{label}">
-                <f:translate key="{label}" default="{label}"/>
-            </f:if>
-        </f:link.external>
+    <f:if condition="{button}">
+        <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a>
     </f:if>
-
 </f:section>
 </html>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html
index bbbac6ee9667fac4db6196d31dae4206052dae25..3e04b449ada86475b93d1157fe397b398316bc0a 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/ListWidget.html
@@ -5,7 +5,7 @@
     <f:if condition="{items}">
         <ul>
             <f:for each="{items}" as="item">
-                <li><f:link.typolink parameter="{item.link}">{item.title}</f:link.typolink></li>
+                <li>{item}</li>
             </f:for>
         </ul>
     </f:if>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html
index 99c210653e29bfb89920698f0448f07ad9515107..361eb0ba58ca322f469e872eb6310af4f3fd8793 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/NumberWithIconWidget.html
@@ -9,9 +9,9 @@
             </div>
         </f:if>
         <div class="widget-number-content">
-            <div class="widget-number-title">{title}</div>
+            <div class="widget-number-title"><f:translate key="{title}" default="{title}" /></div>
             <f:if condition="{subtitle}">
-                <div class="widget-number-subtitle"><small>{subtitle}</small></div>
+                <div class="widget-number-subtitle"><small><f:translate key="{subtitle}" default="{subtitle}" /></small></div>
             </f:if>
             <div class="widget-number-number">{number}</div>
         </div>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html
index 5dd79fb931a1735caafc7f0b8c702ebad1113d37..2a261352455e4fcee21ae9212d78508b6ec887ff 100644
--- a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html
+++ b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/RssWidget.html
@@ -18,12 +18,8 @@
 
 </f:section>
 <f:section name="footer">
-
-    <f:if condition="{moreItemsLink}">
-        <f:link.typolink parameter="{moreItemsLink}" target="_blank" class="widget-cta">
-            <f:translate key="{moreItemsText}" default="{moreItemsText}"/>
-        </f:link.typolink>
+    <f:if condition="{button}">
+        <a href="{button.link}" target="{button.target}" class="widget-cta">{f:translate(id: button.text, default: button.text)}</a>
     </f:if>
-
 </f:section>
 </html>
diff --git a/typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformation.html b/typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformationWidget.html
similarity index 100%
rename from typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformation.html
rename to typo3/sysext/dashboard/Resources/Private/Templates/Widget/T3GeneralInformationWidget.html
diff --git a/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css b/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css
index 8d79f154f43cbbe67ac848babf7c72a9f30c8e6c..df55e386901ba1a0a78a4a5fb3af8bd5af6d02fe 100644
--- a/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css
+++ b/typo3/sysext/dashboard/Resources/Public/Css/dashboard.css
@@ -10,4 +10,4 @@
  * 
  * The TYPO3 project - inspiring people to share!
  */
-.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h4{height:400px}}@media screen and (min-width:750px){.dashboard-item--h6{height:600px}}.dashboard-item--w4{width:100%}@media screen and (min-width:1285px){.dashboard-item--w4{width:50%}}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.widget-number{height:100%;display:flex;align-items:center}.widget-number-icon{display:flex;align-items:center;width:42px;margin-right:20px;color:#000}.widget-number-content{display:flex;flex-direction:column;justify-content:center}.widget-number-title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.widget-number-number{line-height:1.3;font-weight:900;font-size:24px}
\ No newline at end of file
+.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h-medium{height:400px}}@media screen and (min-width:750px){.dashboard-item--h-large{height:600px}}.dashboard-item--w-medium{width:100%}@media screen and (min-width:1285px){.dashboard-item--w-medium{width:50%}}.dashboard-item--w-large{width:100%}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.widget-number{height:100%;display:flex;align-items:center}.widget-number-icon{display:flex;align-items:center;width:42px;margin-right:20px;color:#000}.widget-number-content{display:flex;flex-direction:column;justify-content:center}.widget-number-title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.widget-number-number{line-height:1.3;font-weight:900;font-size:24px}
\ No newline at end of file
diff --git a/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php b/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..13b4176a900dcd842f67b95ab26b214054730cb2
--- /dev/null
+++ b/typo3/sysext/dashboard/Tests/Unit/DependencyInjection/DashboardWidgetPassTest.php
@@ -0,0 +1,226 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Dashboard\Tests\Unit;
+
+/*
+ * 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!
+ */
+
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use TYPO3\CMS\Dashboard\DependencyInjection\DashboardWidgetPass;
+use TYPO3\CMS\Dashboard\WidgetRegistry;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class DashboardWidgetPassTest extends UnitTestCase
+{
+    /**
+     * @var DashboardWidgetPass
+     */
+    protected $subject;
+
+    /**
+     * @var ContainerBuilder
+     */
+    protected $container;
+
+    /**
+     * @var Definition
+     */
+    protected $widgetRegistryDefinition;
+
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        $this->subject = new DashboardWidgetPass('dashboard.widget');
+        $this->container = $this->prophesize(ContainerBuilder::class);
+        $this->widgetRegistryDefinition = $this->prophesize(Definition::class);
+    }
+
+    /**
+     * @test
+     */
+    public function doesNothingIfWidgetRegistryIsUnkown(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn(false);
+        $this->container->findTaggedServiceIds('dashboard.widget')->shouldNotBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+
+    /**
+     * @test
+     */
+    public function doesNothingIfNoWidgetsAreTagged(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal());
+        $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([])->shouldBeCalled();
+        $this->widgetRegistryDefinition->addMethodCall()->shouldNotBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+
+    /**
+     * @test
+     */
+    public function makesWidgetPublic(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal());
+        $this->container->findTaggedServiceIds('dashboard.widget')->willReturn(['NewsWidget' => []]);
+        $definition = $this->prophesize(Definition::class);
+        $this->container->findDefinition('NewsWidget')->willReturn($definition->reveal());
+        $definition->setPublic(true)->shouldBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+
+    /**
+     * @test
+     */
+    public function registersTaggedWidgetWithMiniumConfigurationInRegistry(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal());
+        $definition = $this->prophesize(Definition::class);
+        $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal());
+        $definition->setPublic(true);
+        $definition->setArgument('$configuration', Argument::that(function ($argument) {
+            return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration';
+        }));
+
+        $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([
+            'dashboard.widget.t3news' => [
+                [
+                    'identifier' => 't3news',
+                    'groupNames' => 'typo3',
+                    'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title',
+                    'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description',
+                ]
+            ]
+        ]);
+        $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) {
+            $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration'];
+            /* @var Definition $definition */
+            return $definition instanceof Definition
+                && $definition->getClass(WidgetConfiguration::class)
+                && $definition->getArgument('$identifier') === 't3news'
+                && $definition->getArgument('$groupNames') === ['typo3']
+                && $definition->getArgument('$title') ===  'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
+                && $definition->getArgument('$description') ===  'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
+                && $definition->getArgument('$iconIdentifier') ===  'content-dashboard'
+                && $definition->getArgument('$height') === 'small'
+                && $definition->getArgument('$width') === 'small'
+                ;
+        }))->shouldBeCalled();
+        $this->widgetRegistryDefinition->addMethodCall(
+            'registerWidget',
+            [
+                't3newsWidgetConfiguration',
+            ]
+        )->shouldBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+
+    /**
+     * @test
+     */
+    public function registersWidgetToMultipleGroupsByComma(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal());
+        $definition = $this->prophesize(Definition::class);
+        $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal());
+        $definition->setPublic(true);
+        $definition->setArgument('$configuration', Argument::that(function ($argument) {
+            return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration';
+        }));
+
+        $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([
+            'dashboard.widget.t3news' => [
+                [
+                    'identifier' => 't3news',
+                    'groupNames' => 'typo3, general',
+                    'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title',
+                    'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description',
+                ]
+            ]
+        ]);
+        $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) {
+            $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration'];
+            /* @var Definition $definition */
+            return $definition instanceof Definition
+                && $definition->getClass(WidgetConfiguration::class)
+                && $definition->getArgument('$groupNames') === ['typo3', 'general']
+                ;
+        }))->shouldBeCalled();
+        $this->widgetRegistryDefinition->addMethodCall(
+            'registerWidget',
+            [
+                't3newsWidgetConfiguration',
+            ]
+        )->shouldBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+
+    /**
+     * @test
+     */
+    public function registersTaggedWidgetWithMaximumConfigurationInRegistry(): void
+    {
+        $this->container->findDefinition(WidgetRegistry::class)->willReturn($this->widgetRegistryDefinition->reveal());
+        $definition = $this->prophesize(Definition::class);
+        $this->container->findDefinition('dashboard.widget.t3news')->willReturn($definition->reveal());
+        $definition->setPublic(true);
+        $definition->setArgument('$configuration', Argument::that(function ($argument) {
+            return $argument instanceof Reference && (string)$argument === 't3newsWidgetConfiguration';
+        }));
+
+        $this->container->findTaggedServiceIds('dashboard.widget')->willReturn([
+            'dashboard.widget.t3news' => [
+                [
+                    'identifier' => 't3news',
+                    'groupNames' => 'typo3',
+                    'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title',
+                    'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description',
+                    'iconIdentifier' => 'some-icon',
+                    'height' => 'large',
+                    'width' => 'medium',
+                ]
+            ]
+        ]);
+        $this->container->addDefinitions(Argument::that(function (array $widgetConfigurationDefinitions) {
+            $definition = $widgetConfigurationDefinitions['t3newsWidgetConfiguration'];
+            /* @var Definition $definition */
+            return $definition instanceof Definition
+                && $definition->getClass(WidgetConfiguration::class)
+                && $definition->getArgument('$identifier') === 't3news'
+                && $definition->getArgument('$groupNames') === ['typo3']
+                && $definition->getArgument('$title') ===  'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
+                && $definition->getArgument('$description') ===  'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
+                && $definition->getArgument('$iconIdentifier') ===  'some-icon'
+                && $definition->getArgument('$height') === 'large'
+                && $definition->getArgument('$width') === 'medium'
+                ;
+        }))->shouldBeCalled();
+        $this->widgetRegistryDefinition->addMethodCall(
+            'registerWidget',
+            [
+                't3newsWidgetConfiguration',
+            ]
+        )->shouldBeCalled();
+
+        $this->subject->process($this->container->reveal());
+    }
+}
diff --git a/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php b/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php
index ae2514e29f6c5e158b8d9133a200f263e4c4b0bc..7b245d443addb849f9d634453d538eced1797787 100644
--- a/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php
+++ b/typo3/sysext/dashboard/Tests/Unit/WidgetRegistryTest.php
@@ -16,10 +16,12 @@ namespace TYPO3\CMS\Dashboard\Tests\Unit;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Prophecy\Argument;
 use Prophecy\Prophecy\ObjectProphecy;
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Dashboard\WidgetRegistry;
+use TYPO3\CMS\Dashboard\Widgets\Interfaces\WidgetConfigurationInterface;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 class WidgetRegistryTest extends UnitTestCase
@@ -29,7 +31,9 @@ class WidgetRegistryTest extends UnitTestCase
      */
     protected $resetSingletonInstances = true;
 
-    /** @var WidgetRegistry  */
+    /**
+     * @var WidgetRegistry
+     */
     protected $subject;
 
     /**
@@ -37,13 +41,18 @@ class WidgetRegistryTest extends UnitTestCase
      */
     protected $beUserProphecy;
 
+    /**
+     * @var ContainerInterface|ObjectProphecy
+     */
+    protected $containerProphecy;
+
     public function setUp(): void
     {
         $this->beUserProphecy = $this->prophesize(BackendUserAuthentication::class);
-        $containerProphecy = $this->prophesize(ContainerInterface::class);
+        $this->containerProphecy = $this->prophesize(ContainerInterface::class);
 
         $GLOBALS['BE_USER'] = $this->beUserProphecy->reveal();
-        $this->subject = new WidgetRegistry($containerProphecy->reveal());
+        $this->subject = new WidgetRegistry($this->containerProphecy->reveal());
     }
 
     /**
@@ -63,13 +72,31 @@ class WidgetRegistryTest extends UnitTestCase
      */
     public function getAllWidgetReturnsAllRegisteredWidgets(array $expectedValues, array $widgetsToRegister): void
     {
-        foreach ($widgetsToRegister as $widget) {
-            $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']);
-        }
+        $this->registerWidgets($widgetsToRegister);
 
         self::assertCount((int)$expectedValues['count'], $this->subject->getAllWidgets());
     }
 
+    /**
+     * @param array $expectedValues
+     * @param array $widgetsToRegister
+     *
+     * @test
+     * @dataProvider widgetsToRegister
+     */
+    public function returnsWidgetsForGroup(
+        array $expectedValues,
+        array $widgetsToRegister
+    ): void {
+        $this->registerWidgets($widgetsToRegister);
+        $this->beUserProphecy->check('available_widgets', Argument::any())->willReturn(true);
+
+        self::assertCount(
+            (int)$expectedValues['group1Count'],
+            $this->subject->getAvailableWidgetsForWidgetGroup('group1')
+        );
+    }
+
     /**
      * @param array $expectedValues
      * @param array $widgetsToRegister
@@ -82,7 +109,7 @@ class WidgetRegistryTest extends UnitTestCase
         array $widgetsToRegister
     ): void {
         foreach ($widgetsToRegister as $widget) {
-            $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']);
+            $this->registerWidget($widget);
 
             $this->beUserProphecy
                 ->check(
@@ -107,7 +134,7 @@ class WidgetRegistryTest extends UnitTestCase
         array $widgetsToRegister
     ): void {
         foreach ($widgetsToRegister as $widget) {
-            $this->subject->registerWidget($widget['identifier'], $widget['className'], $widget['groups']);
+            $this->registerWidget($widget);
 
             $this->beUserProphecy
                 ->check(
@@ -121,89 +148,242 @@ class WidgetRegistryTest extends UnitTestCase
         self::assertCount((int)$expectedValues['userCount'], $this->subject->getAvailableWidgets());
     }
 
+    /**
+     * @test
+     */
+    public function addWidgetsInItemsProcFunc(): void
+    {
+        $this->registerWidgets([
+            [
+                'serviceName' => 'dashboard.widget.t3news',
+                'identifier' => 't3orgnews',
+                'groups' => ['typo3'],
+                'iconIdentifier' => 'content-widget-rss',
+                'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                'height' => 4,
+                'width' => 4,
+                'additionalCssClasses' => [],
+            ],
+            [
+                'serviceName' => 'dashboard.widget.t3comnews',
+                'identifier' => '2ndWidget',
+                'groups' => ['typo3'],
+                'iconIdentifier' => 'content-widget-2nd',
+                'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.title',
+                'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.description',
+                'height' => 4,
+                'width' => 4,
+                'additionalCssClasses' => [],
+            ],
+        ]);
+
+        $parameters = [];
+        $this->subject->widgetItemsProcFunc($parameters);
+
+        self::assertEquals(
+            [
+                'items' => [
+                    [
+                        'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        't3orgnews',
+                        'content-widget-rss',
+                        'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                    ],
+                    [
+                        'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.title',
+                        '2ndWidget',
+                        'content-widget-2nd',
+                        'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:2ndWidget.description',
+                    ],
+                ]
+            ],
+            $parameters
+        );
+    }
+
+    private function registerWidgets(array $widgetsToRegister): void
+    {
+        foreach ($widgetsToRegister as $widget) {
+            $this->registerWidget($widget);
+        }
+    }
+
+    private function registerWidget(array $widget)
+    {
+        $widgetConfiguration = $this->prophesize(WidgetConfigurationInterface::class);
+        $widgetConfiguration->getTitle()->willReturn($widget['title']);
+        $widgetConfiguration->getIdentifier()->willReturn($widget['identifier']);
+        $widgetConfiguration->getIconIdentifier()->willReturn($widget['iconIdentifier']);
+        $widgetConfiguration->getDescription()->willReturn($widget['description']);
+        $widgetConfiguration->getGroupNames()->willReturn($widget['groups']);
+        $widgetConfiguration->getHeight()->willReturn($widget['height']);
+        $widgetConfiguration->getWidth()->willReturn($widget['width']);
+        $widgetConfiguration->getAdditionalCssClasses()->willReturn($widget['additionalCssClasses']);
+
+        $this->containerProphecy->get($widget['serviceName'])->willReturn($widgetConfiguration->reveal());
+        $this->subject->registerWidget($widget['serviceName']);
+    }
+
     public function widgetsToRegister(): array
     {
         return [
-            [
+            'Single widget' => [
                 [
                     'count' => 1,
                     'adminCount' => 1,
-                    'userCount' => 1
+                    'userCount' => 1,
+                    'group1Count' => 1,
                 ],
                 [
                     [
                         'identifier' => 'test-widget1',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3news',
                         'groups' => ['group1'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => true
                     ]
                 ]
             ],
-            [
+            'Two widgets' => [
                 [
                     'count' => 2,
                     'adminCount' => 2,
-                    'userCount' => 1
+                    'userCount' => 1,
+                    'group1Count' => 2,
                 ],
                 [
                     [
                         'identifier' => 'test-widget1',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3news',
                         'groups' => ['group1'],
-                        'availableForUser' => true
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
+                        'availableForUser' => true,
                     ],
                     [
                         'identifier' => 'test-widget2',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3comnews',
                         'groups' => ['group1'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => false
                     ],
                 ]
             ],
-            [
+            'Three widgets, two having same identifier' => [
                 [
                     'count' => 2,
                     'adminCount' => 2,
-                    'userCount' => 2
+                    'userCount' => 2,
+                    'group1Count' => 1,
                 ],
                 [
                     [
                         'identifier' => 'test-widget1',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3news',
                         'groups' => ['group1'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => true
                     ],
                     [
                         'identifier' => 'test-widget1',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3news',
                         'groups' => ['group1'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => true
                     ],
                     [
                         'identifier' => 'test-widget2',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3orgnews',
                         'groups' => ['group2'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => true
                     ],
                 ]
             ],
-            [
+            'Two widgets, one not available for user' => [
                 [
                     'count' => 2,
                     'adminCount' => 2,
-                    'userCount' => 1
+                    'userCount' => 1,
+                    'group1Count' => 1,
                 ],
                 [
                     [
                         'identifier' => 'test-widget1',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
+                        'serviceName' => 'dashboard.widget.t3news',
                         'groups' => ['group1'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => true
                     ],
                     [
                         'identifier' => 'test-widget2',
-                        'className' => 'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget',
-                        'groups' => ['group1', 'group2'],
+                        'serviceName' => 'dashboard.widget.t3comnews',
+                        'groups' => ['group2'],
+                        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.title',
+                        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/Widgets.xlf:T3OrgNews.description',
+                        'iconIdentifier' => 'content-widget-rss',
+                        'height' => 2,
+                        'width' => 4,
+                        'additionalCssClasses' => [
+                            'custom-widget',
+                            'rss-condensed',
+                        ],
                         'availableForUser' => false
                     ],
                 ]
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
index 8afba7fb77eeb6420878ce2f786d6dcb73505f4a..42d542fafbfaf96cff47c50b9cbcbe3042f2027d 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
@@ -1412,4 +1412,69 @@ return [
             'Deprecation-90937-VariousHooksInContentObjectRenderer.rst',
         ],
     ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractChartWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractCtaButtonWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractListWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractNumberWithIconWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractRssWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\AbstractWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\DocumentationGettingStartedWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\DocumentationTSconfigReferenceWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\T3GeneralInformation' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\T3NewsWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
+    'TYPO3\CMS\Dashboard\Widgets\T3SecurityAdvisoriesWidget' => [
+        'restFiles' => [
+            'Breaking-90660-RegistrationOfWidgetsChanged.rst',
+        ],
+    ],
 ];