diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt
index 7dcbe1721..d5fa9dd71 100644
--- a/es-core/CMakeLists.txt
+++ b/es-core/CMakeLists.txt
@@ -41,6 +41,7 @@ set(CORE_HEADERS
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h
+        ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h
@@ -116,6 +117,7 @@ set(CORE_SOURCES
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp
+        ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp
         ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
diff --git a/es-core/src/components/BadgesComponent.cpp b/es-core/src/components/BadgesComponent.cpp
index a8ac67636..b65b0c5d5 100644
--- a/es-core/src/components/BadgesComponent.cpp
+++ b/es-core/src/components/BadgesComponent.cpp
@@ -8,24 +8,16 @@
 //
 
 #include "components/BadgesComponent.h"
-#include <numeric>
 
 #include "Settings.h"
 #include "ThemeData.h"
 #include "resources/TextureResource.h"
 
 BadgesComponent::BadgesComponent(Window* window)
-    : GuiComponent(window)
-    , mDirection(DEFAULT_DIRECTION)
-    , mWrap(DEFAULT_WRAP)
-    , mJustifyContent(DEFAULT_JUSTIFY_CONTENT)
-    , mAlign(DEFAULT_ALIGN)
+    : FlexboxComponent(window, NUM_SLOTS)
 {
-    mSlots = std::vector<std::string>();
-    mSlots.push_back(SLOT_FAVORITE);
-    mSlots.push_back(SLOT_COMPLETED);
-    mSlots.push_back(SLOT_KIDS);
-    mSlots.push_back(SLOT_BROKEN);
+    // Define the slots.
+    setSlots({SLOT_FAVORITE, SLOT_COMPLETED, SLOT_KIDS, SLOT_BROKEN});
 
     mBadgeIcons = std::map<std::string, std::string>();
     mBadgeIcons[SLOT_FAVORITE] = ":/graphics/badge_favorite.png";
@@ -33,33 +25,34 @@ BadgesComponent::BadgesComponent(Window* window)
     mBadgeIcons[SLOT_KIDS] = ":/graphics/badge_kidgame.png";
     mBadgeIcons[SLOT_BROKEN] = ":/graphics/badge_broken.png";
 
-    mTextures = std::map<std::string, std::shared_ptr<TextureResource>>();
-    mTextures[SLOT_FAVORITE] = TextureResource::get(mBadgeIcons[SLOT_FAVORITE], true);
-    mTextures[SLOT_COMPLETED] = TextureResource::get(mBadgeIcons[SLOT_COMPLETED], true);
-    mTextures[SLOT_KIDS] = TextureResource::get(mBadgeIcons[SLOT_KIDS], true);
-    mTextures[SLOT_BROKEN] = TextureResource::get(mBadgeIcons[SLOT_BROKEN], true);
-
-    mVertices = std::map<std::string, Renderer::Vertex[4]>();
+    // Create the child ImageComponent for every badge.
+    mImageComponents = std::map<std::string, ImageComponent>();
+    ImageComponent mImageFavorite = ImageComponent(window);
+    mImageFavorite.setImage(mBadgeIcons[SLOT_FAVORITE], false, false);
+    mImageComponents.insert({SLOT_FAVORITE, mImageFavorite});
+    ImageComponent mImageCompleted = ImageComponent(window);
+    mImageCompleted.setImage(mBadgeIcons[SLOT_COMPLETED], false, false);
+    mImageComponents.insert({SLOT_COMPLETED, mImageCompleted});
+    ImageComponent mImageKids = ImageComponent(window);
+    mImageKids.setImage(mBadgeIcons[SLOT_KIDS], false, false);
+    mImageComponents.insert({SLOT_KIDS, mImageKids});
+    ImageComponent mImageBroken = ImageComponent(window);
+    mImageBroken.setImage(mBadgeIcons[SLOT_BROKEN], false, false);
+    mImageComponents.insert({SLOT_BROKEN, mImageBroken});
 
     // TODO: Should be dependent on the direction property.
     mSize = glm::vec2{64.0f * NUM_SLOTS, 64.0f};
 
-    // TODO: Add definition for default value.
-    mMargin = glm::vec2{10.0f, 10.0f};
-
-    updateVertices();
+    // Trigger initial layout computation.
+    onSizeChanged();
 }
 
 void BadgesComponent::setValue(const std::string& value)
 {
-    if (value.empty()) {
-        mSlots.clear();
-    }
-    else {
-        // Start by clearing the slots.
-        mSlots.clear();
+    std::vector<std::string> slots = {};
 
-        // Interpret the value and iteratively fill mSlots. The value is a space separated list of
+    if (!value.empty()) {
+        // Interpret the value and iteratively fill slots. The value is a space separated list of
         // strings.
         std::string temp;
         std::istringstream ss(value);
@@ -68,271 +61,25 @@ void BadgesComponent::setValue(const std::string& value)
                   temp == SLOT_BROKEN))
                 LOG(LogError) << "Badge slot '" << temp << "' is invalid.";
             else
-                mSlots.push_back(temp);
+                slots.push_back(temp);
         }
     }
 
-    updateVertices();
+    setSlots(slots);
+    onSizeChanged();
 }
 
 std::string BadgesComponent::getValue() const
 {
+    const std::vector<std::string> slots = getSlots();
     std::stringstream ss;
-    for (auto& slot : mSlots)
+    for (auto& slot : slots)
         ss << slot << ' ';
     std::string r = ss.str();
     r.pop_back();
     return r;
 }
 
-void BadgesComponent::onSizeChanged()
-{
-    // TODO: Should be dependent on the direction property.
-    if (mSize.y == 0.0f)
-        mSize.y = mSize.x / NUM_SLOTS;
-    else if (mSize.x == 0.0f)
-        mSize.x = mSize.y * NUM_SLOTS;
-
-    if (mSize.y > 0.0f) {
-        size_t heightPx = static_cast<size_t>(std::round(mSize.y));
-        for (auto const& tex : mTextures)
-            tex.second->rasterizeAt(heightPx, heightPx);
-    }
-
-    updateVertices();
-}
-
-void BadgesComponent::updateVertices()
-{
-    mVertices.clear();
-
-    /*const float numSlots = mSlots.size();
-    float s;
-    if (mDirection == DIRECTION_ROW)
-        s = std::min( getSize().x / numSlots, getSize().y );
-    else
-        s = std::min( getSize().y / numSlots, getSize().x );
-    const long color = 4278190080;
-
-    int i = 0;
-    for (auto & slot : mSlots)
-    {
-        // clang-format off
-        mVertices[slot][0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color};
-        mVertices[slot][1] = {{0.0f, s},    {0.0f, 0.0f}, color};
-        mVertices[slot][2] = {{s   , 0.0f}, {1.0f, 1.0f}, color};
-        mVertices[slot][3] = {{s   , s},    {1.0f, 0.0f}, color};
-        // clang-format on
-        i++;
-    }*/
-
-    // The maximum number of badges to be displayed.
-    const float numSlots = NUM_SLOTS;
-
-    // The available size to draw in.
-    const auto size = getSize();
-
-    // Compute the number of rows and columns and the item max dimensions.
-    int rows;
-    int columns;
-    float itemWidth;
-    float itemHeight;
-
-    if (mDirection == DIRECTION_ROW) {
-        if (mWrap != WRAP_NOWRAP) {
-            // Suppose we have i rows, what would be the average area of an icon? Compute for a
-            // small number of rows.
-            std::vector<float> areas;
-            for (int i = 1; i < 10; i++) {
-
-                float area = size.x * size.y;
-
-                // Number of vertical gaps.
-                int verticalGaps = i - 1;
-
-                // Area of vertical gaps.
-                area -= verticalGaps * mMargin.y * size.x;
-
-                // Height per item.
-                float iHeight = (size.y - verticalGaps * mMargin.y) / i;
-
-                // Width per item. (Approximation)
-                // TODO: this is an approximation!
-                // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight =
-                // 0;
-                float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x;
-
-                // Average area available per badge
-                float avgArea = iHeight * iWidth;
-
-                // Push to the areas array.
-                areas.push_back(avgArea);
-            }
-
-            // Determine the number of rows based on what results in the largest area per badge
-            // based on available space.
-            rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1;
-
-            // Obtain final item dimensions.
-            itemHeight = (size.y - (rows - 1) * mMargin.y) / rows;
-            itemWidth = areas[rows - 1] / itemHeight;
-
-            // Compute number of columns.
-            if (rows == 1)
-                columns = NUM_SLOTS;
-            else
-                columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x));
-        }
-        else {
-            rows = 1;
-            columns = NUM_SLOTS;
-            itemHeight = size.y;
-            itemWidth = size.x / (NUM_SLOTS + (NUM_SLOTS - 1) * mMargin.x);
-        }
-    }
-    else {
-        // TODO: Add computation for column direction.
-    }
-
-    const long color = 4278190080;
-    if (mDirection == DIRECTION_ROW) {
-
-        // Start row.
-        int row = mWrap == WRAP_REVERSE ? rows : 1;
-        int item = 0;
-
-        // Iterate through all the rows.
-        for (int c = 0; c < rows && item < mSlots.size(); c++) {
-
-            // Pre-compute dimensions of all items in this row.
-            std::vector<float> widths;
-            std::vector<float> heights;
-            int itemTemp = item;
-            for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) {
-                glm::vec texSize = mTextures[mSlots[itemTemp]]->getSize();
-                float aspectRatioTexture = texSize.x / texSize.y;
-                float aspectRatioItemSpace = itemWidth / itemHeight;
-                if (aspectRatioTexture > aspectRatioItemSpace) {
-                    widths.push_back(itemWidth);
-                    heights.push_back(itemWidth / aspectRatioTexture);
-                }
-                else {
-                    widths.push_back(itemHeight * aspectRatioTexture);
-                    heights.push_back(itemHeight);
-                }
-                itemTemp++;
-            }
-
-            // Iterate through the columns.
-            float xpos = 0;
-            for (int column = 0; column < columns && item < mSlots.size(); column++) {
-
-                // We always go from left to right.
-                // Here we compute the coordinates of the items.
-
-                // Compute final badge x position.
-                float x;
-                float totalWidth =
-                    std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) +
-                    (widths.size() - 1) * mMargin.x;
-                if (mJustifyContent == "start") {
-                    x = xpos;
-                    xpos += widths[column] + mMargin.x;
-                }
-                else if (mJustifyContent == "end") {
-                    if (column == 0)
-                        xpos += size.x - totalWidth;
-                    x = xpos;
-                    xpos += widths[column] + mMargin.x;
-                }
-                else if (mJustifyContent == "center") {
-                    if (column == 0)
-                        xpos += (size.x - totalWidth) / 2;
-                    x = xpos;
-                    xpos += widths[column] + mMargin.x;
-                }
-                else if (mJustifyContent == "space-between") {
-                    float gapSize = (size.x - totalWidth) / (widths.size() - 1);
-                    x = xpos;
-                    xpos += widths[column] + gapSize;
-                }
-                else if (mJustifyContent == "space-around") {
-                    float gapSize = (size.x - totalWidth) / (widths.size() - 1);
-                    xpos += gapSize / 2;
-                    x = xpos;
-                    xpos += widths[column] + gapSize / 2;
-                }
-                else if (mJustifyContent == "space-evenly") {
-                    float gapSize = (size.x - totalWidth) / (widths.size() + 1);
-                    xpos += gapSize;
-                    x = xpos;
-                }
-
-                // Compute final badge y position.
-                float y = row * itemHeight;
-                if (mAlign == "end") {
-                    y += itemHeight - heights[column];
-                }
-                else if (mAlign == "center") {
-                    y += (itemHeight - heights[column]) / 2;
-                }
-                if (mAlign == "stretch") {
-                    heights[column] = itemHeight;
-                }
-
-                LOG(LogError) << "Computed Final Item Position. Row: " << row
-                              << ", Column: " << column << ", Item: " << item << ", pos: (" << x
-                              << ", " << y << "), size: (" << widths[column] << ", "
-                              << heights[column] << ")";
-
-                // Store the item's vertices and apply texture mapping.
-                // clang-format off
-                mVertices[mSlots[item]][0] = {{x, y},                                       {0.0f, 1.0f}, color};
-                mVertices[mSlots[item]][1] = {{x, y+heights[column]},                       {0.0f, 0.0f}, color};
-                mVertices[mSlots[item]][2] = {{x+widths[column]   , y},                     {1.0f, 1.0f}, color};
-                mVertices[mSlots[item]][3] = {{x+widths[column]   , y+heights[column]},     {1.0f, 0.0f}, color};
-                // clang-format on
-
-                // Increment item;
-                item++;
-            }
-
-            // Iterate the row.
-            mWrap == WRAP_REVERSE ? row-- : row++;
-        }
-    }
-}
-
-void BadgesComponent::render(const glm::mat4& parentTrans)
-{
-    if (!isVisible())
-        return;
-
-    glm::mat4 trans{parentTrans * getTransform()};
-
-    Renderer::setMatrix(trans);
-
-    if (mOpacity > 0) {
-        if (Settings::getInstance()->getBool("DebugImage"))
-            Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033);
-
-        for (auto& slot : mSlots) {
-            if (mTextures[slot] == nullptr)
-                continue;
-
-            if (mTextures[slot]->bind()) {
-                Renderer::drawTriangleStrips(mVertices[slot], 4);
-                Renderer::bindTexture(0);
-            }
-
-            // TODO: update render matrix to position of next slot
-            // trans = glm::translate(trans, {0.0f, 0.0f, 1.0f});
-        }
-    }
-
-    renderChildren(trans);
-}
-
 void BadgesComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
                                  const std::string& view,
                                  const std::string& element,
@@ -345,30 +92,20 @@ void BadgesComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
         return;
 
     bool imgChanged = false;
-    for (auto& slot : mSlots) {
+    const std::vector<std::string> slots = getSlots();
+    for (auto& slot : slots) {
         if (properties & PATH && elem->has(slot)) {
             mBadgeIcons[slot] = elem->get<std::string>(slot);
-            mTextures[slot] = TextureResource::get(mBadgeIcons[slot], true);
+            mImageComponents.find(slot)->second.setImage(mBadgeIcons[slot]);
             imgChanged = true;
         }
     }
 
-    if (properties & DIRECTION && elem->has("direction"))
-        mDirection = elem->get<std::string>("direction");
-
-    if (elem->has("wrap"))
-        mWrap = elem->get<std::string>("wrap");
-
-    if (elem->has("justifyContent"))
-        mJustifyContent = elem->get<std::string>("justifyContent");
-
-    if (elem->has("align"))
-        mAlign = elem->get<std::string>("align");
-
     if (elem->has("slots"))
         setValue(elem->get<std::string>("slots"));
 
-    GuiComponent::applyTheme(theme, view, element, properties);
+    // Apply theme on the flexbox component parent.
+    FlexboxComponent::applyTheme(theme, view, element, properties);
 
     if (imgChanged)
         onSizeChanged();
diff --git a/es-core/src/components/BadgesComponent.h b/es-core/src/components/BadgesComponent.h
index 906463cca..715714057 100644
--- a/es-core/src/components/BadgesComponent.h
+++ b/es-core/src/components/BadgesComponent.h
@@ -10,39 +10,20 @@
 #ifndef ES_APP_COMPONENTS_BADGES_COMPONENT_H
 #define ES_APP_COMPONENTS_BADGES_COMPONENT_H
 
+#include "FlexboxComponent.h"
 #include "GuiComponent.h"
+#include "ImageComponent.h"
 #include "renderers/Renderer.h"
 
-#define DIRECTION_ROW "row"
-#define DIRECTION_COLUMN "column"
-#define WRAP_WRAP "wrap"
-#define WRAP_NOWRAP "nowrap"
-#define WRAP_REVERSE "wrap-reverse"
-#define JUSTIFY_CONTENT_START "start"
-#define JUSTIFY_CONTENT_END "end"
-#define JUSTIFY_CONTENT_CENTER "center"
-#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between"
-#define JUSTIFY_CONTENT_SPACE_AROUND "space-around"
-#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly"
-#define ITEM_ALIGN_START "start"
-#define ITEM_ALIGN_END "end"
-#define ITEM_ALIGN_CENTER "center"
-#define ITEM_ALIGN_STRETCH "stretch"
 #define NUM_SLOTS 4
 #define SLOT_FAVORITE "favorite"
 #define SLOT_COMPLETED "completed"
 #define SLOT_KIDS "kidgame"
 #define SLOT_BROKEN "broken"
-#define DEFAULT_DIRECTION DIRECTION_ROW
-#define DEFAULT_WRAP WRAP_WRAP
-#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START
-#define DEFAULT_ALIGN ITEM_ALIGN_CENTER
-#define DEFAULT_MARGIN_X = 10.0f
-#define DEFAULT_MARGIN_Y = 10.0f
 
 class TextureResource;
 
-class BadgesComponent : public GuiComponent
+class BadgesComponent : public FlexboxComponent
 {
 public:
     BadgesComponent(Window* window);
@@ -51,18 +32,6 @@ public:
     // Should be a list of strings.
     void setValue(const std::string& value) override;
 
-    void render(const glm::mat4& parentTrans) override;
-
-    void onSizeChanged() override;
-
-    void setDirection(int direction);
-
-    int getDirection();
-
-    void setSlots(std::vector<std::string>);
-
-    std::vector<std::string> getSlots();
-
     virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
                             const std::string& view,
                             const std::string& element,
@@ -71,18 +40,8 @@ public:
     virtual std::vector<HelpPrompt> getHelpPrompts() override;
 
 private:
-    void updateVertices();
-    std::map<std::string, Renderer::Vertex[4]> mVertices;
-
     std::map<std::string, std::string> mBadgeIcons;
-    std::map<std::string, std::shared_ptr<TextureResource>> mTextures;
-
-    std::string mDirection;
-    std::string mWrap;
-    std::string mJustifyContent;
-    std::string mAlign;
-    glm::vec2 mMargin;
-    std::vector<std::string> mSlots;
+    std::map<std::string, ImageComponent> mImageComponents;
 };
 
 #endif // ES_APP_COMPONENTS_BADGES_COMPONENT_H
diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp
new file mode 100644
index 000000000..210b2d2f2
--- /dev/null
+++ b/es-core/src/components/FlexboxComponent.cpp
@@ -0,0 +1,281 @@
+//  SPDX-License-Identifier: MIT
+//
+//  EmulationStation Desktop Edition
+//  FlexboxComponent.cpp
+//
+//  Flexbox layout component.
+//  Used by gamelist views.
+//
+
+#include "components/FlexboxComponent.h"
+#include <numeric>
+
+#include "Settings.h"
+#include "ThemeData.h"
+#include "resources/TextureResource.h"
+
+FlexboxComponent::FlexboxComponent(Window* window, unsigned int assumeChildren)
+    : GuiComponent(window)
+    , mDirection(DEFAULT_DIRECTION)
+    , mWrap(DEFAULT_WRAP)
+    , mJustifyContent(DEFAULT_JUSTIFY_CONTENT)
+    , mAlign(DEFAULT_ALIGN)
+    , mAssumeChildren(assumeChildren)
+{
+
+    // Initialize contents of the flexbox.
+    mSlots = std::vector<std::string>();
+    mComponents = std::map<std::string, GuiComponent>();
+
+    // Initialize flexbox layout.
+    mVertices = std::map<std::string, glm::vec4>();
+
+    // TODO: Should be dependent on the direction property.
+    mSize = glm::vec2{64.0f * mAssumeChildren, 64.0f};
+
+    // TODO: Add definition for default value.
+    mMargin = glm::vec2{10.0f, 10.0f};
+
+    // Calculate flexbox layout.
+    updateVertices();
+}
+
+void FlexboxComponent::onSizeChanged()
+{
+    // TODO: Should be dependent on the direction property.
+    if (mSize.y == 0.0f)
+        mSize.y = mSize.x / mAssumeChildren;
+    else if (mSize.x == 0.0f)
+        mSize.x = mSize.y * mAssumeChildren;
+
+    updateVertices();
+}
+
+void FlexboxComponent::updateVertices()
+{
+    // The maximum number of components to be displayed.
+    const float numSlots = mAssumeChildren;
+
+    // The available size to draw in.
+    const auto size = getSize();
+
+    // Compute the number of rows and columns and the item max dimensions.
+    int rows;
+    int columns;
+    float itemWidth;
+    float itemHeight;
+    if (mDirection == DIRECTION_ROW) {
+        if (mWrap != WRAP_NOWRAP) {
+            // Suppose we have i rows, what would be the average area of an icon? Compute for a
+            // small number of rows.
+            std::vector<float> areas;
+            for (int i = 1; i < 10; i++) {
+
+                float area = size.x * size.y;
+
+                // Number of vertical gaps.
+                int verticalGaps = i - 1;
+
+                // Area of vertical gaps.
+                area -= verticalGaps * mMargin.y * size.x;
+
+                // Height per item.
+                float iHeight = (size.y - verticalGaps * mMargin.y) / i;
+
+                // Width per item. (Approximation)
+                // TODO: this is an approximation!
+                // Solve: area - (iHeight * (iWidth + mMargin.x) * numSlots) + mMargin.x * iHeight =
+                // 0;
+                float iWidth = ((area + mMargin.x * iHeight) / (iHeight * numSlots)) - mMargin.x;
+
+                // Average area available per badge
+                float avgArea = iHeight * iWidth;
+
+                // Push to the areas array.
+                areas.push_back(avgArea);
+            }
+
+            // Determine the number of rows based on what results in the largest area per badge
+            // based on available space.
+            rows = std::max_element(areas.begin(), areas.end()) - areas.begin() + 1;
+
+            // Obtain final item dimensions.
+            itemHeight = (size.y - (rows - 1) * mMargin.y) / rows;
+            itemWidth = areas[rows - 1] / itemHeight;
+
+            // Compute number of columns.
+            if (rows == 1)
+                columns = mAssumeChildren;
+            else
+                columns = std::round((size.x + mMargin.x) / (itemWidth + mMargin.x));
+        }
+        else {
+            rows = 1;
+            columns = mAssumeChildren;
+            itemHeight = size.y;
+            itemWidth = size.x / (mAssumeChildren + (mAssumeChildren - 1) * mMargin.x);
+        }
+    }
+    else {
+        // TODO: Add computation for column direction.
+    }
+
+    // Compute the exact positions and sizes of the components.
+    mVertices.clear();
+    if (mDirection == DIRECTION_ROW) {
+
+        // Start row.
+        int row = mWrap == WRAP_REVERSE ? rows : 1;
+        int item = 0;
+
+        // Iterate through all the rows.
+        for (int c = 0; c < rows && item < mSlots.size(); c++) {
+
+            // Pre-compute dimensions of all items in this row.
+            std::vector<float> widths;
+            std::vector<float> heights;
+            int itemTemp = item;
+            for (int column = 0; column < columns && itemTemp < mSlots.size(); column++) {
+                glm::vec componentSize = mComponents.find(mSlots[itemTemp])->second.getSize();
+                float aspectRatioTexture = componentSize.x / componentSize.y;
+                float aspectRatioItemSpace = itemWidth / itemHeight;
+                if (aspectRatioTexture > aspectRatioItemSpace) {
+                    widths.push_back(itemWidth);
+                    heights.push_back(itemWidth / aspectRatioTexture);
+                }
+                else {
+                    widths.push_back(itemHeight * aspectRatioTexture);
+                    heights.push_back(itemHeight);
+                }
+                itemTemp++;
+            }
+
+            // Iterate through the columns.
+            float xpos = 0;
+            for (int column = 0; column < columns && item < mSlots.size(); column++) {
+
+                // We always go from left to right.
+                // Here we compute the coordinates of the items.
+
+                // Compute final badge x position.
+                float x;
+                float totalWidth =
+                    std::accumulate(widths.begin(), widths.end(), decltype(widths)::value_type(0)) +
+                    (widths.size() - 1) * mMargin.x;
+                if (mJustifyContent == "start") {
+                    x = xpos;
+                    xpos += widths[column] + mMargin.x;
+                }
+                else if (mJustifyContent == "end") {
+                    if (column == 0)
+                        xpos += size.x - totalWidth;
+                    x = xpos;
+                    xpos += widths[column] + mMargin.x;
+                }
+                else if (mJustifyContent == "center") {
+                    if (column == 0)
+                        xpos += (size.x - totalWidth) / 2;
+                    x = xpos;
+                    xpos += widths[column] + mMargin.x;
+                }
+                else if (mJustifyContent == "space-between") {
+                    float gapSize = (size.x - totalWidth) / (widths.size() - 1);
+                    x = xpos;
+                    xpos += widths[column] + gapSize;
+                }
+                else if (mJustifyContent == "space-around") {
+                    float gapSize = (size.x - totalWidth) / (widths.size() - 1);
+                    xpos += gapSize / 2;
+                    x = xpos;
+                    xpos += widths[column] + gapSize / 2;
+                }
+                else if (mJustifyContent == "space-evenly") {
+                    float gapSize = (size.x - totalWidth) / (widths.size() + 1);
+                    xpos += gapSize;
+                    x = xpos;
+                }
+
+                // Compute final badge y position.
+                float y = row * itemHeight;
+                if (mAlign == "end") {
+                    y += itemHeight - heights[column];
+                }
+                else if (mAlign == "center") {
+                    y += (itemHeight - heights[column]) / 2;
+                }
+                if (mAlign == "stretch") {
+                    heights[column] = itemHeight;
+                }
+
+                LOG(LogError) << "Computed Final Item Position. Row: " << row
+                              << ", Column: " << column << ", Item: " << item << ", pos: (" << x
+                              << ", " << y << "), size: (" << widths[column] << ", "
+                              << heights[column] << ")";
+
+                // Store the item's layout.
+                mVertices[mSlots[item]] = {x, y, widths[column], heights[column]};
+
+                // Increment item;
+                item++;
+            }
+
+            // Iterate the row.
+            mWrap == WRAP_REVERSE ? row-- : row++;
+        }
+    }
+}
+
+void FlexboxComponent::render(const glm::mat4& parentTrans)
+{
+    if (!isVisible())
+        return;
+
+    // Render all the child components.
+    for (unsigned int i = 0; i < mSlots.size(); i++) {
+        glm::vec4 v = mVertices[mSlots[i]];
+        auto c = mComponents.find(mSlots[i])->second;
+        glm::vec2 oldSize = c.getSize();
+        c.setPosition(v.x, v.y);
+        c.setSize(v.z, v.w);
+        c.render(parentTrans);
+        c.setSize(oldSize);
+    }
+
+    renderChildren(parentTrans);
+}
+
+void FlexboxComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
+                                  const std::string& view,
+                                  const std::string& element,
+                                  unsigned int properties)
+{
+    using namespace ThemeFlags;
+
+    // TODO: How to do this without explicit 'badges' property?
+    const ThemeData::ThemeElement* elem = theme->getElement(view, element, "badges");
+    if (!elem)
+        return;
+
+    if (properties & DIRECTION && elem->has("direction"))
+        mDirection = elem->get<std::string>("direction");
+
+    if (elem->has("wrap"))
+        mWrap = elem->get<std::string>("wrap");
+
+    if (elem->has("justifyContent"))
+        mJustifyContent = elem->get<std::string>("justifyContent");
+
+    if (elem->has("align"))
+        mAlign = elem->get<std::string>("align");
+
+    GuiComponent::applyTheme(theme, view, element, properties);
+
+    // Trigger layout computation.
+    onSizeChanged();
+}
+
+std::vector<HelpPrompt> FlexboxComponent::getHelpPrompts()
+{
+    std::vector<HelpPrompt> prompts;
+    return prompts;
+}
diff --git a/es-core/src/components/FlexboxComponent.h b/es-core/src/components/FlexboxComponent.h
new file mode 100644
index 000000000..0ae106bd8
--- /dev/null
+++ b/es-core/src/components/FlexboxComponent.h
@@ -0,0 +1,85 @@
+//  SPDX-License-Identifier: MIT
+//
+//  EmulationStation Desktop Edition
+//  FlexboxComponent.h
+//
+//  Flexbox layout component.
+//  Used by gamelist views.
+//
+
+#ifndef ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H
+#define ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H
+
+#include "GuiComponent.h"
+#include "renderers/Renderer.h"
+
+#define DIRECTION_ROW "row"
+#define DIRECTION_COLUMN "column"
+#define WRAP_WRAP "wrap"
+#define WRAP_NOWRAP "nowrap"
+#define WRAP_REVERSE "wrap-reverse"
+#define JUSTIFY_CONTENT_START "start"
+#define JUSTIFY_CONTENT_END "end"
+#define JUSTIFY_CONTENT_CENTER "center"
+#define JUSTIFY_CONTENT_SPACE_BETWEEN "space-between"
+#define JUSTIFY_CONTENT_SPACE_AROUND "space-around"
+#define JUSTIFY_CONTENT_SPACE_EVENLY "space-evenly"
+#define ITEM_ALIGN_START "start"
+#define ITEM_ALIGN_END "end"
+#define ITEM_ALIGN_CENTER "center"
+#define ITEM_ALIGN_STRETCH "stretch"
+#define DEFAULT_DIRECTION DIRECTION_ROW
+#define DEFAULT_WRAP WRAP_WRAP
+#define DEFAULT_JUSTIFY_CONTENT JUSTIFY_CONTENT_START
+#define DEFAULT_ALIGN ITEM_ALIGN_CENTER
+#define DEFAULT_MARGIN_X = 10.0f
+#define DEFAULT_MARGIN_Y = 10.0f
+
+class TextureResource;
+
+class FlexboxComponent : public GuiComponent
+{
+public:
+    FlexboxComponent(Window* window, unsigned int assumeChildren = 0);
+
+    void render(const glm::mat4& parentTrans) override;
+
+    void onSizeChanged() override;
+
+    void setDirection(int direction);
+
+    int getDirection();
+
+    void setSlots(std::vector<std::string>);
+
+    std::vector<std::string> getSlots() const;
+
+    virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
+                            const std::string& view,
+                            const std::string& element,
+                            unsigned int properties) override;
+
+    virtual std::vector<HelpPrompt> getHelpPrompts() override;
+
+private:
+    // Calculate flexbox layout.
+    void updateVertices();
+
+    // Storage for the flexbox components positions and sizes.
+    std::map<std::string, glm::vec4> mVertices;
+
+    // The components of the flexbox.
+    std::map<std::string, GuiComponent> mComponents;
+
+    // Named map of the components of the flexbox.
+    std::vector<std::string> mSlots;
+
+    std::string mDirection;
+    std::string mWrap;
+    std::string mJustifyContent;
+    std::string mAlign;
+    glm::vec2 mMargin;
+    unsigned int mAssumeChildren;
+};
+
+#endif // ES_APP_COMPONENTS_FLEXBOX_COMPONENT_H