diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp
index d2cc89520c..c203af8a48 100644
--- a/apps/esmtool/record.cpp
+++ b/apps/esmtool/record.cpp
@@ -1171,7 +1171,7 @@ namespace EsmTool
     template <>
     void Record<ESM::Race>::print()
     {
-        static const char* sAttributeNames[8]
+        static const char* sAttributeNames[ESM::Attribute::Length]
             = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck" };
 
         std::cout << "  Name: " << mData.mName << std::endl;
diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp
index 5dd2e16ae8..149b5d19ca 100644
--- a/apps/opencs/model/world/refidadapterimp.cpp
+++ b/apps/opencs/model/world/refidadapterimp.cpp
@@ -7,6 +7,7 @@
 #include <apps/opencs/model/world/refiddata.hpp>
 #include <apps/opencs/model/world/universalid.hpp>
 
+#include <components/esm/attr.hpp>
 #include <components/esm3/loadcont.hpp>
 #include <components/esm3/loadmgef.hpp>
 #include <components/esm3/loadskil.hpp>
@@ -1007,8 +1008,7 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn
 int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(
     const RefIdColumn* column, const RefIdData& data, int index) const
 {
-    // There are 8 attributes
-    return 8;
+    return ESM::Attribute::Length;
 }
 
 void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow(
@@ -1385,8 +1385,7 @@ int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(
 int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(
     const RefIdColumn* column, const RefIdData& data, int index) const
 {
-    // There are 8 attributes
-    return 8;
+    return ESM::Attribute::Length;
 }
 
 void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow(
diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp
index fec0b4260f..b92422c43a 100644
--- a/apps/openmw/mwclass/clothing.cpp
+++ b/apps/openmw/mwclass/clothing.cpp
@@ -123,7 +123,7 @@ namespace MWClass
         const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>();
         static const ESM::RefId ringUp = ESM::RefId::stringRefId("Item Ring Up");
         static const ESM::RefId clothsUp = ESM::RefId::stringRefId("Item Clothes Up");
-        if (ref->mBase->mData.mType == 8)
+        if (ref->mBase->mData.mType == ESM::Clothing::Ring)
         {
             return ringUp;
         }
@@ -135,7 +135,7 @@ namespace MWClass
         const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>();
         static const ESM::RefId ringDown = ESM::RefId::stringRefId("Item Ring Down");
         static const ESM::RefId clothsDown = ESM::RefId::stringRefId("Item Clothes Down");
-        if (ref->mBase->mData.mType == 8)
+        if (ref->mBase->mData.mType == ESM::Clothing::Ring)
         {
             return ringDown;
         }
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 5f475ec61d..6c37b72b5b 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -88,11 +88,12 @@ namespace
 
         bool male = (npc->mFlags & ESM::NPC::Female) == 0;
 
+        const auto& attributes = MWBase::Environment::get().getESMStore()->get<ESM::Attribute>();
         int level = creatureStats.getLevel();
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
+        for (const ESM::Attribute& attribute : attributes)
         {
-            const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
-            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
+            const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[attribute.mId];
+            creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale);
         }
 
         // class bonus
@@ -102,18 +103,19 @@ namespace
         {
             if (attribute >= 0 && attribute < ESM::Attribute::Length)
             {
-                creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10);
+                auto id = static_cast<ESM::Attribute::AttributeID>(attribute);
+                creatureStats.setAttribute(id, creatureStats.getAttribute(id).getBase() + 10);
             }
         }
 
         // skill bonus
-        for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute)
+        for (const ESM::Attribute& attribute : attributes)
         {
             float modifierSum = 0;
 
             for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
             {
-                if (skill.mData.mAttribute != attribute)
+                if (skill.mData.mAttribute != attribute.mId)
                     continue;
 
                 // is this a minor or major skill?
@@ -127,9 +129,10 @@ namespace
                 }
                 modifierSum += add;
             }
-            creatureStats.setAttribute(attribute,
+            creatureStats.setAttribute(attribute.mId,
                 std::min(
-                    round_ieee_754(creatureStats.getAttribute(attribute).getBase() + (level - 1) * modifierSum), 100));
+                    round_ieee_754(creatureStats.getAttribute(attribute.mId).getBase() + (level - 1) * modifierSum),
+                    100));
         }
 
         // initial health
@@ -223,13 +226,10 @@ namespace
                     100)); // Must gracefully handle level 0
         }
 
-        int attributes[ESM::Attribute::Length];
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-            attributes[i] = npcStats.getAttribute(i).getBase();
-
         if (!spellsInitialised)
         {
-            std::vector<ESM::RefId> spells = MWMechanics::autoCalcNpcSpells(npcStats.getSkills(), attributes, race);
+            std::vector<ESM::RefId> spells
+                = MWMechanics::autoCalcNpcSpells(npcStats.getSkills(), npcStats.getAttributes(), race);
             npcStats.getSpells().addAllToInstance(spells);
         }
     }
diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp
index b4841d6cbe..e05f1b37c2 100644
--- a/apps/openmw/mwdialogue/filter.cpp
+++ b/apps/openmw/mwdialogue/filter.cpp
@@ -382,9 +382,10 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
                 .getModified(false);
 
         case SelectWrapper::Function_PcAttribute:
-
-            return player.getClass().getCreatureStats(player).getAttribute(select.getArgument()).getModified();
-
+        {
+            auto attribute = static_cast<ESM::Attribute::AttributeID>(select.getArgument());
+            return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified();
+        }
         case SelectWrapper::Function_PcSkill:
         {
             ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument());
@@ -653,8 +654,10 @@ bool MWDialogue::Filter::hasFactionRankSkillRequirements(
 
     MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
 
-    return stats.getAttribute(faction.mData.mAttribute[0]).getBase() >= faction.mData.mRankData[rank].mAttribute1
-        && stats.getAttribute(faction.mData.mAttribute[1]).getBase() >= faction.mData.mRankData[rank].mAttribute2;
+    return stats.getAttribute(ESM::Attribute::AttributeID(faction.mData.mAttribute[0])).getBase()
+        >= faction.mData.mRankData[rank].mAttribute1
+        && stats.getAttribute(ESM::Attribute::AttributeID(faction.mData.mAttribute[1])).getBase()
+        >= faction.mData.mRankData[rank].mAttribute2;
 }
 
 bool MWDialogue::Filter::hasFactionRankReputationRequirements(
diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp
index dc1608b9c7..114238482d 100644
--- a/apps/openmw/mwgui/charactercreation.cpp
+++ b/apps/openmw/mwgui/charactercreation.cpp
@@ -102,21 +102,11 @@ namespace MWGui
             mPlayerSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
     }
 
-    void CharacterCreation::setValue(std::string_view id, const MWMechanics::AttributeValue& value)
+    void CharacterCreation::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value)
     {
-        std::string_view prefix = "AttribVal";
-        if (id.starts_with(prefix) && id.size() == prefix.size() + 1)
-        {
-            char index = id[prefix.size()];
-            if (index >= '1' && index <= '8')
-            {
-                // Match [AttribVal1-AttribVal8] to the corresponding AttributeID values [0-7]
-                auto attribute = static_cast<ESM::Attribute::AttributeID>(index - '0' - 1);
-                mPlayerAttributes[attribute] = value;
-                if (mReviewDialog)
-                    mReviewDialog->setAttribute(attribute, value);
-            }
-        }
+        mPlayerAttributes[id] = value;
+        if (mReviewDialog)
+            mReviewDialog->setAttribute(id, value);
     }
 
     void CharacterCreation::setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value)
diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp
index f9e76d1fc6..d590c0cfd6 100644
--- a/apps/openmw/mwgui/charactercreation.hpp
+++ b/apps/openmw/mwgui/charactercreation.hpp
@@ -44,7 +44,7 @@ namespace MWGui
         // Show a dialog
         void spawnDialog(const char id);
 
-        void setValue(std::string_view id, const MWMechanics::AttributeValue& value) override;
+        void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) override;
         void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override;
         void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override;
         void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) override;
diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp
index b26d750191..202f18ad35 100644
--- a/apps/openmw/mwgui/class.cpp
+++ b/apps/openmw/mwgui/class.cpp
@@ -260,8 +260,8 @@ namespace MWGui
         mSpecializationName->setCaption(specName);
         ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization);
 
-        mFavoriteAttribute[0]->setAttributeId(klass->mData.mAttribute[0]);
-        mFavoriteAttribute[1]->setAttributeId(klass->mData.mAttribute[1]);
+        mFavoriteAttribute[0]->setAttributeId(static_cast<ESM::Attribute::AttributeID>(klass->mData.mAttribute[0]));
+        mFavoriteAttribute[1]->setAttributeId(static_cast<ESM::Attribute::AttributeID>(klass->mData.mAttribute[1]));
         ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId());
         ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId());
 
@@ -749,30 +749,34 @@ namespace MWGui
         center();
 
         const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Attribute>();
+        MyGUI::ScrollView* attributes;
+        getWidget(attributes, "Attributes");
+        MyGUI::IntCoord coord{ 0, 0, attributes->getWidth(), 18 };
         for (const ESM::Attribute& attribute : store)
         {
-            Widgets::MWAttributePtr widget;
-            char theIndex = '0' + attribute.mId;
-
-            getWidget(widget, std::string("Attribute").append(1, theIndex));
+            auto* widget
+                = attributes->createWidget<Widgets::MWAttribute>("MW_StatNameButtonC", coord, MyGUI::Align::Default);
+            coord.top += coord.height;
             widget->setAttributeId(attribute.mId);
             widget->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked);
-            ToolTips::createAttributeToolTip(widget, widget->getAttributeId());
+            ToolTips::createAttributeToolTip(widget, attribute.mId);
         }
 
+        attributes->setVisibleVScroll(false);
+        attributes->setCanvasSize(MyGUI::IntSize(attributes->getWidth(), std::max(attributes->getHeight(), coord.top)));
+        attributes->setVisibleVScroll(true);
+        attributes->setViewOffset(MyGUI::IntPoint());
+
         MyGUI::Button* cancelButton;
         getWidget(cancelButton, "CancelButton");
         cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked);
     }
 
-    SelectAttributeDialog::~SelectAttributeDialog() {}
-
     // widget controls
 
     void SelectAttributeDialog::onAttributeClicked(Widgets::MWAttributePtr _sender)
     {
-        // TODO: Change MWAttribute to set and get AttributeID enum instead of int
-        mAttributeId = static_cast<ESM::Attribute::AttributeID>(_sender->getAttributeId());
+        mAttributeId = _sender->getAttributeId();
         eventItemSelected();
     }
 
diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp
index 1052dab581..b231a64b1d 100644
--- a/apps/openmw/mwgui/class.hpp
+++ b/apps/openmw/mwgui/class.hpp
@@ -184,7 +184,7 @@ namespace MWGui
     {
     public:
         SelectAttributeDialog();
-        ~SelectAttributeDialog();
+        ~SelectAttributeDialog() override = default;
 
         bool exit() override;
 
diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
index 45ba20c597..b13fdbeeb9 100644
--- a/apps/openmw/mwgui/levelupdialog.cpp
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -3,8 +3,11 @@
 #include <MyGUI_Button.h>
 #include <MyGUI_EditBox.h>
 #include <MyGUI_ImageBox.h>
+#include <MyGUI_ScrollView.h>
+#include <MyGUI_TextBox.h>
 
 #include <components/fallback/fallback.hpp>
+#include <components/widgets/box.hpp>
 
 #include "../mwbase/environment.hpp"
 #include "../mwbase/soundmanager.hpp"
@@ -12,6 +15,7 @@
 #include "../mwbase/world.hpp"
 
 #include "../mwworld/class.hpp"
+#include "../mwworld/esmstore.hpp"
 
 #include "../mwmechanics/actorutil.hpp"
 #include "../mwmechanics/creaturestats.hpp"
@@ -20,9 +24,13 @@
 #include "class.hpp"
 #include "ustring.hpp"
 
+namespace
+{
+    constexpr unsigned int sMaxCoins = 3;
+    constexpr int sColumnOffsets[] = { 32, 218 };
+}
 namespace MWGui
 {
-    const unsigned int LevelupDialog::sMaxCoins = 3;
     LevelupDialog::LevelupDialog()
         : WindowBase("openmw_levelup_dialog.layout")
         , mCoinCount(sMaxCoins)
@@ -36,23 +44,43 @@ namespace MWGui
 
         mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onOkButtonClicked);
 
-        for (int i = 1; i < 9; ++i)
         {
-            MyGUI::TextBox* t;
-            getWidget(t, "AttribVal" + MyGUI::utility::toString(i));
-            mAttributeValues.push_back(t);
+            const auto& store = MWBase::Environment::get().getESMStore()->get<ESM::Attribute>();
+            const size_t perCol
+                = static_cast<size_t>(std::ceil(store.getSize() / static_cast<float>(std::size(sColumnOffsets))));
+            size_t i = 0;
+            for (const ESM::Attribute& attribute : store)
+            {
+                const int offset = sColumnOffsets[i / perCol];
+                const int row = static_cast<int>(i % perCol);
+                Widgets widgets;
+                widgets.mMultiplier = mAssignWidget->createWidget<MyGUI::TextBox>(
+                    "SandTextVCenter", { offset, 20 * row, 100, 20 }, MyGUI::Align::Default);
+                auto* hbox = mAssignWidget->createWidget<Gui::HBox>(
+                    {}, { offset + 20, 20 * row, 200, 20 }, MyGUI::Align::Default);
+                widgets.mButton = hbox->createWidget<Gui::AutoSizedButton>("SandTextButton", {}, MyGUI::Align::Default);
+                widgets.mButton->setUserData(attribute.mId);
+                widgets.mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked);
+                widgets.mButton->setUserString("TextPadding", "0 0");
+                widgets.mButton->setUserString("ToolTipType", "Layout");
+                widgets.mButton->setUserString("ToolTipLayout", "AttributeToolTip");
+                widgets.mButton->setUserString("Caption_AttributeName", attribute.mName);
+                widgets.mButton->setUserString("Caption_AttributeDescription", attribute.mDescription);
+                widgets.mButton->setUserString("ImageTexture_AttributeImage", attribute.mIcon);
+                widgets.mButton->setCaption(attribute.mName);
+                widgets.mValue = hbox->createWidget<Gui::AutoSizedTextBox>("SandText", {}, MyGUI::Align::Default);
+                mAttributeWidgets.emplace(attribute.mId, widgets);
+                ++i;
+            }
 
-            MyGUI::Button* b;
-            getWidget(b, "Attrib" + MyGUI::utility::toString(i));
-            b->setUserData(i - 1);
-            b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked);
-            mAttributes.push_back(b);
-
-            getWidget(t, "AttribMultiplier" + MyGUI::utility::toString(i));
-            mAttributeMultipliers.push_back(t);
+            mAssignWidget->setVisibleVScroll(false);
+            mAssignWidget->setCanvasSize(MyGUI::IntSize(
+                mAssignWidget->getWidth(), std::max(mAssignWidget->getHeight(), static_cast<int>(20 * perCol))));
+            mAssignWidget->setVisibleVScroll(true);
+            mAssignWidget->setViewOffset(MyGUI::IntPoint());
         }
 
-        for (unsigned int i = 0; i < mCoinCount; ++i)
+        for (unsigned int i = 0; i < sMaxCoins; ++i)
         {
             MyGUI::ImageBox* image = mCoinBox->createWidget<MyGUI::ImageBox>(
                 "ImageBox", MyGUI::IntCoord(0, 0, 16, 16), MyGUI::Align::Default);
@@ -69,24 +97,24 @@ namespace MWGui
         MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player);
         MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
 
-        for (int i = 0; i < 8; ++i)
+        for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get<ESM::Attribute>())
         {
-            int val = creatureStats.getAttribute(i).getBase();
-            if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), i) != mSpentAttributes.end())
+            int val = creatureStats.getAttribute(attribute.mId).getBase();
+            if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute.mId) != mSpentAttributes.end())
             {
-                val += pcStats.getLevelupAttributeMultiplier(i);
+                val += pcStats.getLevelupAttributeMultiplier(attribute.mId);
             }
 
             if (val >= 100)
                 val = 100;
 
-            mAttributeValues[i]->setCaption(MyGUI::utility::toString(val));
+            mAttributeWidgets[attribute.mId].mValue->setCaption(MyGUI::utility::toString(val));
         }
     }
 
     void LevelupDialog::resetCoins()
     {
-        const int coinSpacing = 33;
+        constexpr int coinSpacing = 33;
         int curX = mCoinBox->getWidth() / 2 - (coinSpacing * (mCoinCount - 1) + 16 * mCoinCount) / 2;
         for (unsigned int i = 0; i < sMaxCoins; ++i)
         {
@@ -113,13 +141,15 @@ namespace MWGui
             image->detachFromWidget();
             image->attachToWidget(mAssignWidget);
 
-            int attribute = mSpentAttributes[i];
+            const auto& attribute = mSpentAttributes[i];
+            const auto& widgets = mAttributeWidgets[attribute];
 
-            int xdiff = mAttributeMultipliers[attribute]->getCaption().empty() ? 0 : 20;
+            const int xdiff = widgets.mMultiplier->getCaption().empty() ? 0 : 20;
+            const auto* hbox = widgets.mButton->getParent();
 
-            MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mAssignWidget->getAbsolutePosition()
-                - MyGUI::IntPoint(22 + xdiff, 0);
-            pos.top += (mAttributes[attribute]->getHeight() - image->getHeight()) / 2;
+            MyGUI::IntPoint pos = hbox->getPosition();
+            pos.left -= 22 + xdiff;
+            pos.top += (hbox->getHeight() - image->getHeight()) / 2;
             image->setPosition(pos);
         }
 
@@ -130,8 +160,8 @@ namespace MWGui
     {
         MWBase::World* world = MWBase::Environment::get().getWorld();
         MWWorld::Ptr player = world->getPlayerPtr();
-        MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player);
-        MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
+        const MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player);
+        const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
 
         setClassImage(mClassImage,
             ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0),
@@ -149,28 +179,28 @@ namespace MWGui
         mLevelDescription->setCaption(toUString(levelupdescription));
 
         unsigned int availableAttributes = 0;
-        for (int i = 0; i < 8; ++i)
+        for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get<ESM::Attribute>())
         {
-            MyGUI::TextBox* text = mAttributeMultipliers[i];
-            if (pcStats.getAttribute(i).getBase() < 100)
+            const auto& widgets = mAttributeWidgets[attribute.mId];
+            if (pcStats.getAttribute(attribute.mId).getBase() < 100)
             {
-                mAttributes[i]->setEnabled(true);
-                mAttributeValues[i]->setEnabled(true);
+                widgets.mButton->setEnabled(true);
+                widgets.mValue->setEnabled(true);
                 availableAttributes++;
 
-                float mult = pcStats.getLevelupAttributeMultiplier(i);
-                mult = std::min(mult, 100 - pcStats.getAttribute(i).getBase());
+                float mult = pcStats.getLevelupAttributeMultiplier(attribute.mId);
+                mult = std::min(mult, 100 - pcStats.getAttribute(attribute.mId).getBase());
                 if (mult <= 1)
-                    text->setCaption({});
+                    widgets.mMultiplier->setCaption({});
                 else
-                    text->setCaption("x" + MyGUI::utility::toString(mult));
+                    widgets.mMultiplier->setCaption("x" + MyGUI::utility::toString(mult));
             }
             else
             {
-                mAttributes[i]->setEnabled(false);
-                mAttributeValues[i]->setEnabled(false);
+                widgets.mButton->setEnabled(false);
+                widgets.mValue->setEnabled(false);
 
-                text->setCaption({});
+                widgets.mMultiplier->setCaption({});
             }
         }
 
@@ -215,9 +245,9 @@ namespace MWGui
 
     void LevelupDialog::onAttributeClicked(MyGUI::Widget* sender)
     {
-        int attribute = *sender->getUserData<int>();
+        auto attribute = *sender->getUserData<ESM::Attribute::AttributeID>();
 
-        std::vector<int>::iterator found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute);
+        auto found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute);
         if (found != mSpentAttributes.end())
             mSpentAttributes.erase(found);
         else
diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp
index 790db888cb..f3a80ebb9d 100644
--- a/apps/openmw/mwgui/levelupdialog.hpp
+++ b/apps/openmw/mwgui/levelupdialog.hpp
@@ -1,6 +1,8 @@
 #ifndef MWGUI_LEVELUPDIALOG_H
 #define MWGUI_LEVELUPDIALOG_H
 
+#include <components/esm/attr.hpp>
+
 #include "windowbase.hpp"
 
 namespace MWGui
@@ -14,23 +16,26 @@ namespace MWGui
         void onOpen() override;
 
     private:
+        struct Widgets
+        {
+            MyGUI::Button* mButton;
+            MyGUI::TextBox* mValue;
+            MyGUI::TextBox* mMultiplier;
+        };
         MyGUI::Button* mOkButton;
         MyGUI::ImageBox* mClassImage;
         MyGUI::TextBox* mLevelText;
         MyGUI::EditBox* mLevelDescription;
 
         MyGUI::Widget* mCoinBox;
-        MyGUI::Widget* mAssignWidget;
+        MyGUI::ScrollView* mAssignWidget;
 
-        std::vector<MyGUI::Button*> mAttributes;
-        std::vector<MyGUI::TextBox*> mAttributeValues;
-        std::vector<MyGUI::TextBox*> mAttributeMultipliers;
+        std::map<ESM::Attribute::AttributeID, Widgets> mAttributeWidgets;
         std::vector<MyGUI::ImageBox*> mCoins;
 
-        std::vector<int> mSpentAttributes;
+        std::vector<ESM::Attribute::AttributeID> mSpentAttributes;
 
         unsigned int mCoinCount;
-        static const unsigned int sMaxCoins;
 
         void onOkButtonClicked(MyGUI::Widget* sender);
         void onAttributeClicked(MyGUI::Widget* sender);
diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp
index 8887b9eb4f..39ab84246d 100644
--- a/apps/openmw/mwgui/review.cpp
+++ b/apps/openmw/mwgui/review.cpp
@@ -76,14 +76,22 @@ namespace MWGui
 
         // Setup attributes
 
-        Widgets::MWAttributePtr widget;
+        MyGUI::Widget* attributes = getWidget("Attributes");
         const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Attribute>();
+        MyGUI::IntCoord coord{ 8, 4, 250, 18 };
         for (const ESM::Attribute& attribute : store)
         {
-            getWidget(widget, std::string("Attribute").append(1, '0' + attribute.mId));
+            auto* widget
+                = attributes->createWidget<Widgets::MWAttribute>("MW_StatNameValue", coord, MyGUI::Align::Default);
             mAttributeWidgets.emplace(attribute.mId, widget);
+            widget->setUserString("ToolTipType", "Layout");
+            widget->setUserString("ToolTipLayout", "AttributeToolTip");
+            widget->setUserString("Caption_AttributeName", attribute.mName);
+            widget->setUserString("Caption_AttributeDescription", attribute.mDescription);
+            widget->setUserString("ImageTexture_AttributeImage", attribute.mIcon);
             widget->setAttributeId(attribute.mId);
             widget->setAttributeValue(Widgets::MWAttribute::AttributeValue());
+            coord.top += coord.height;
         }
 
         // Setup skills
@@ -193,7 +201,7 @@ namespace MWGui
 
     void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value)
     {
-        std::map<int, Widgets::MWAttributePtr>::iterator attr = mAttributeWidgets.find(static_cast<int>(attributeId));
+        auto attr = mAttributeWidgets.find(attributeId);
         if (attr == mAttributeWidgets.end())
             return;
 
@@ -394,9 +402,9 @@ namespace MWGui
         if (!mRaceId.empty())
             race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(mRaceId);
 
-        int attributes[ESM::Attribute::Length];
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-            attributes[i] = mAttributeWidgets[i]->getAttributeValue().getBase();
+        std::map<ESM::Attribute::AttributeID, MWMechanics::AttributeValue> attributes;
+        for (const auto& [key, value] : mAttributeWidgets)
+            attributes[key] = value->getAttributeValue();
 
         std::vector<ESM::RefId> selectedSpells = MWMechanics::autoCalcPlayerSpells(mSkillValues, attributes, race);
         for (ESM::RefId& spellId : selectedSpells)
diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp
index dcb63d4d05..fe2a509fa6 100644
--- a/apps/openmw/mwgui/review.hpp
+++ b/apps/openmw/mwgui/review.hpp
@@ -90,7 +90,7 @@ namespace MWGui
 
         Widgets::MWDynamicStatPtr mHealth, mMagicka, mFatigue;
 
-        std::map<int, Widgets::MWAttributePtr> mAttributeWidgets;
+        std::map<ESM::Attribute::AttributeID, Widgets::MWAttributePtr> mAttributeWidgets;
 
         std::vector<ESM::RefId> mMajorSkills, mMinorSkills, mMiscSkills;
         std::map<ESM::RefId, MWMechanics::SkillValue> mSkillValues;
diff --git a/apps/openmw/mwgui/statswatcher.cpp b/apps/openmw/mwgui/statswatcher.cpp
index dab6b9bf82..b18073f7ad 100644
--- a/apps/openmw/mwgui/statswatcher.cpp
+++ b/apps/openmw/mwgui/statswatcher.cpp
@@ -35,14 +35,16 @@ namespace MWGui
         if (mWatched.isEmpty())
             return;
 
+        const auto& store = MWBase::Environment::get().getESMStore();
         MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
         const MWMechanics::NpcStats& stats = mWatched.getClass().getNpcStats(mWatched);
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
+        for (const ESM::Attribute& attribute : store->get<ESM::Attribute>())
         {
-            if (stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty)
+            const auto& value = stats.getAttribute(attribute.mId);
+            if (value != mWatchedAttributes[attribute.mId] || mWatchedStatsEmpty)
             {
-                mWatchedAttributes[i] = stats.getAttribute(i);
-                setValue("AttribVal" + std::to_string(i + 1), stats.getAttribute(i));
+                mWatchedAttributes[attribute.mId] = value;
+                setValue(attribute.mId, value);
             }
         }
 
@@ -83,7 +85,7 @@ namespace MWGui
             }
         }
 
-        for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
+        for (const ESM::Skill& skill : store->get<ESM::Skill>())
         {
             const auto& value = stats.getSkill(skill.mId);
             if (value != mWatchedSkills[skill.mId] || mWatchedStatsEmpty)
@@ -112,16 +114,14 @@ namespace MWGui
             if (watchedRecord->mRace != mWatchedRace || mWatchedStatsEmpty)
             {
                 mWatchedRace = watchedRecord->mRace;
-                const ESM::Race* race
-                    = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(watchedRecord->mRace);
+                const ESM::Race* race = store->get<ESM::Race>().find(watchedRecord->mRace);
                 setValue("race", race->mName);
             }
 
             if (watchedRecord->mClass != mWatchedClass || mWatchedStatsEmpty)
             {
                 mWatchedClass = watchedRecord->mClass;
-                const ESM::Class* cls
-                    = MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(watchedRecord->mClass);
+                const ESM::Class* cls = store->get<ESM::Class>().find(watchedRecord->mClass);
                 setValue("class", cls->mName);
 
                 size_t size = cls->mData.mSkills.size();
@@ -151,7 +151,7 @@ namespace MWGui
         mListeners.erase(listener);
     }
 
-    void StatsWatcher::setValue(std::string_view id, const MWMechanics::AttributeValue& value)
+    void StatsWatcher::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value)
     {
         for (StatsListener* listener : mListeners)
             listener->setValue(id, value);
diff --git a/apps/openmw/mwgui/statswatcher.hpp b/apps/openmw/mwgui/statswatcher.hpp
index e7339294da..d4a9e54243 100644
--- a/apps/openmw/mwgui/statswatcher.hpp
+++ b/apps/openmw/mwgui/statswatcher.hpp
@@ -19,7 +19,7 @@ namespace MWGui
         virtual ~StatsListener() = default;
 
         /// Set value for the given ID.
-        virtual void setValue(std::string_view id, const MWMechanics::AttributeValue& value) {}
+        virtual void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) {}
         virtual void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) {}
         virtual void setValue(std::string_view, const std::string& value) {}
         virtual void setValue(std::string_view, int value) {}
@@ -31,7 +31,7 @@ namespace MWGui
     {
         MWWorld::Ptr mWatched;
 
-        MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length];
+        std::map<ESM::Attribute::AttributeID, MWMechanics::AttributeValue> mWatchedAttributes;
         std::map<ESM::RefId, MWMechanics::SkillValue> mWatchedSkills;
 
         MWMechanics::DynamicStat<float> mWatchedHealth;
@@ -50,7 +50,7 @@ namespace MWGui
 
         std::set<StatsListener*> mListeners;
 
-        void setValue(std::string_view id, const MWMechanics::AttributeValue& value);
+        void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value);
         void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value);
         void setValue(std::string_view id, const std::string& value);
         void setValue(std::string_view id, int value);
diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
index 4bd82faa70..76c1b065dd 100644
--- a/apps/openmw/mwgui/statswindow.cpp
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -1,5 +1,6 @@
 #include "statswindow.hpp"
 
+#include <MyGUI_Button.h>
 #include <MyGUI_Gui.h>
 #include <MyGUI_ImageBox.h>
 #include <MyGUI_InputManager.h>
@@ -43,15 +44,26 @@ namespace MWGui
         , mMinFullWidth(mMainWidget->getSize().width)
     {
 
-        const char* names[][2] = { { "Attrib1", "sAttributeStrength" }, { "Attrib2", "sAttributeIntelligence" },
-            { "Attrib3", "sAttributeWillpower" }, { "Attrib4", "sAttributeAgility" }, { "Attrib5", "sAttributeSpeed" },
-            { "Attrib6", "sAttributeEndurance" }, { "Attrib7", "sAttributePersonality" },
-            { "Attrib8", "sAttributeLuck" }, { 0, 0 } };
-
         const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
-        for (int i = 0; names[i][0]; ++i)
+        MyGUI::Widget* attributeView = getWidget("AttributeView");
+        MyGUI::IntCoord coord{ 0, 0, 204, 18 };
+        const MyGUI::Align alignment = MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch;
+        for (const ESM::Attribute& attribute : store.get<ESM::Attribute>())
         {
-            setText(names[i][0], store.get<ESM::GameSetting>().find(names[i][1])->mValue.getString());
+            auto* box = attributeView->createWidget<MyGUI::Button>({}, coord, alignment);
+            box->setUserString("ToolTipType", "Layout");
+            box->setUserString("ToolTipLayout", "AttributeToolTip");
+            box->setUserString("Caption_AttributeName", attribute.mName);
+            box->setUserString("Caption_AttributeDescription", attribute.mDescription);
+            box->setUserString("ImageTexture_AttributeImage", attribute.mIcon);
+            coord.top += coord.height;
+            auto* name = box->createWidget<MyGUI::TextBox>("SandText", { 0, 0, 160, 18 }, alignment);
+            name->setNeedMouseFocus(false);
+            name->setCaption(attribute.mName);
+            auto* value = box->createWidget<MyGUI::TextBox>(
+                "SandTextRight", { 160, 0, 44, 18 }, MyGUI::Align::Right | MyGUI::Align::Top);
+            value->setNeedMouseFocus(false);
+            mAttributeWidgets.emplace(attribute.mId, value);
         }
 
         getWidget(mSkillView, "SkillView");
@@ -143,37 +155,20 @@ namespace MWGui
         mMainWidget->castType<MyGUI::Window>()->setCaption(playerName);
     }
 
-    void StatsWindow::setValue(std::string_view id, const MWMechanics::AttributeValue& value)
+    void StatsWindow::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value)
     {
-        static const char* ids[] = {
-            "AttribVal1",
-            "AttribVal2",
-            "AttribVal3",
-            "AttribVal4",
-            "AttribVal5",
-            "AttribVal6",
-            "AttribVal7",
-            "AttribVal8",
-            nullptr,
-        };
-
-        for (int i = 0; ids[i]; ++i)
-            if (ids[i] == id)
-            {
-                setText(id, std::to_string(static_cast<int>(value.getModified())));
-
-                MyGUI::TextBox* box;
-                getWidget(box, id);
-
-                if (value.getModified() > value.getBase())
-                    box->_setWidgetState("increased");
-                else if (value.getModified() < value.getBase())
-                    box->_setWidgetState("decreased");
-                else
-                    box->_setWidgetState("normal");
-
-                break;
-            }
+        auto it = mAttributeWidgets.find(id);
+        if (it != mAttributeWidgets.end())
+        {
+            MyGUI::TextBox* box = it->second;
+            box->setCaption(std::to_string(static_cast<int>(value.getModified())));
+            if (value.getModified() > value.getBase())
+                box->_setWidgetState("increased");
+            else if (value.getModified() < value.getBase())
+                box->_setWidgetState("decreased");
+            else
+                box->_setWidgetState("normal");
+        }
     }
 
     void StatsWindow::setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value)
diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp
index ca7a5a7e07..92926c1180 100644
--- a/apps/openmw/mwgui/statswindow.hpp
+++ b/apps/openmw/mwgui/statswindow.hpp
@@ -3,6 +3,7 @@
 
 #include "statswatcher.hpp"
 #include "windowpinnablebase.hpp"
+#include <components/esm/attr.hpp>
 #include <components/esm/refid.hpp>
 
 namespace MWGui
@@ -21,7 +22,7 @@ namespace MWGui
         void setPlayerName(const std::string& playerName);
 
         /// Set value for the given ID.
-        void setValue(std::string_view id, const MWMechanics::AttributeValue& value) override;
+        void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) override;
         void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override;
         void setValue(std::string_view id, const std::string& value) override;
         void setValue(std::string_view id, int value) override;
@@ -67,6 +68,7 @@ namespace MWGui
 
         std::vector<ESM::RefId> mMajorSkills, mMinorSkills, mMiscSkills;
         std::map<ESM::RefId, MWMechanics::SkillValue> mSkillValues;
+        std::map<ESM::Attribute::AttributeID, MyGUI::TextBox*> mAttributeWidgets;
         std::map<ESM::RefId, std::pair<MyGUI::TextBox*, MyGUI::TextBox*>> mSkillWidgetMap;
         std::map<std::string, MyGUI::Widget*> mFactionWidgetMap;
         FactionList mFactions; ///< Stores a list of factions and the current rank
diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp
index 6867b9114d..7fa58589df 100644
--- a/apps/openmw/mwgui/tooltips.cpp
+++ b/apps/openmw/mwgui/tooltips.cpp
@@ -823,7 +823,7 @@ namespace MWGui
         widget->setUserString("ImageTexture_SkillNoProgressImage", skill->mIcon);
     }
 
-    void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId)
+    void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, ESM::Attribute::AttributeID attributeId)
     {
         const ESM::Attribute* attribute
             = MWBase::Environment::get().getESMStore()->get<ESM::Attribute>().search(attributeId);
diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp
index 8246b0ca3b..4fa0803eaa 100644
--- a/apps/openmw/mwgui/tooltips.hpp
+++ b/apps/openmw/mwgui/tooltips.hpp
@@ -94,7 +94,7 @@ namespace MWGui
         // these do not create an actual tooltip, but they fill in the data that is required so the tooltip
         // system knows what to show in case this widget is hovered
         static void createSkillToolTip(MyGUI::Widget* widget, ESM::RefId skillId);
-        static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId);
+        static void createAttributeToolTip(MyGUI::Widget* widget, ESM::Attribute::AttributeID attributeId);
         static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId);
         static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId);
         static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace);
diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp
index 785a6b48c2..90c092dd79 100644
--- a/apps/openmw/mwgui/trainingwindow.cpp
+++ b/apps/openmw/mwgui/trainingwindow.cpp
@@ -150,7 +150,8 @@ namespace MWGui
         }
 
         // You can not train a skill above its governing attribute
-        if (pcStats.getSkill(skill->mId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
+        if (pcStats.getSkill(skill->mId).getBase()
+            >= pcStats.getAttribute(ESM::Attribute::AttributeID(skill->mData.mAttribute)).getBase())
         {
             MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}");
             return;
diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp
index 194fa972af..de4f954438 100644
--- a/apps/openmw/mwgui/widgets.cpp
+++ b/apps/openmw/mwgui/widgets.cpp
@@ -102,13 +102,13 @@ namespace MWGui::Widgets
     /* MWAttribute */
 
     MWAttribute::MWAttribute()
-        : mId(-1)
+        : mId(ESM::Attribute::Length)
         , mAttributeNameWidget(nullptr)
         , mAttributeValueWidget(nullptr)
     {
     }
 
-    void MWAttribute::setAttributeId(int attributeId)
+    void MWAttribute::setAttributeId(ESM::Attribute::AttributeID attributeId)
     {
         mId = attributeId;
         updateWidgets();
@@ -129,17 +129,15 @@ namespace MWGui::Widgets
     {
         if (mAttributeNameWidget)
         {
-            if (mId < 0 || mId >= 8)
+            const ESM::Attribute* attribute
+                = MWBase::Environment::get().getESMStore()->get<ESM::Attribute>().search(mId);
+            if (!attribute)
             {
                 mAttributeNameWidget->setCaption({});
             }
             else
             {
-                static const std::string_view attributes[8]
-                    = { "sAttributeStrength", "sAttributeIntelligence", "sAttributeWillpower", "sAttributeAgility",
-                          "sAttributeSpeed", "sAttributeEndurance", "sAttributePersonality", "sAttributeLuck" };
-                MyGUI::UString name = toUString(
-                    MWBase::Environment::get().getWindowManager()->getGameSettingString(attributes[mId], {}));
+                MyGUI::UString name = toUString(attribute->mName);
                 mAttributeNameWidget->setCaption(name);
             }
         }
@@ -156,8 +154,6 @@ namespace MWGui::Widgets
         }
     }
 
-    MWAttribute::~MWAttribute() {}
-
     void MWAttribute::initialiseOverride()
     {
         Base::initialiseOverride();
diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp
index 90e9680a90..8e45b37f2d 100644
--- a/apps/openmw/mwgui/widgets.hpp
+++ b/apps/openmw/mwgui/widgets.hpp
@@ -7,6 +7,7 @@
 #include <MyGUI_TextBox.h>
 #include <MyGUI_Widget.h>
 
+#include <components/esm/attr.hpp>
 #include <components/esm/refid.hpp>
 #include <components/esm3/effectlist.hpp>
 #include <components/esm3/loadskil.hpp>
@@ -32,8 +33,6 @@ namespace MWGui
     {
         class MWEffectList;
 
-        void fixTexturePath(std::string& path);
-
         struct SpellEffectParams
         {
             SpellEffectParams()
@@ -139,10 +138,10 @@ namespace MWGui
 
             typedef MWMechanics::AttributeValue AttributeValue;
 
-            void setAttributeId(int attributeId);
+            void setAttributeId(ESM::Attribute::AttributeID attributeId);
             void setAttributeValue(const AttributeValue& value);
 
-            int getAttributeId() const { return mId; }
+            ESM::Attribute::AttributeID getAttributeId() const { return mId; }
             const AttributeValue& getAttributeValue() const { return mValue; }
 
             // Events
@@ -154,7 +153,7 @@ namespace MWGui
             EventHandle_AttributeVoid eventClicked;
 
         protected:
-            virtual ~MWAttribute();
+            ~MWAttribute() override = default;
 
             void initialiseOverride() override;
 
@@ -163,7 +162,7 @@ namespace MWGui
         private:
             void updateWidgets();
 
-            int mId;
+            ESM::Attribute::AttributeID mId;
             AttributeValue mValue;
             MyGUI::TextBox* mAttributeNameWidget;
             MyGUI::TextBox* mAttributeValueWidget;
diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp
index 1687b202d7..da4d2a4614 100644
--- a/apps/openmw/mwlua/stats.cpp
+++ b/apps/openmw/mwlua/stats.cpp
@@ -182,9 +182,10 @@ namespace MWLua
         template <class G>
         sol::object get(const Context& context, std::string_view prop, G getter) const
         {
+            auto id = static_cast<ESM::Attribute::AttributeID>(mIndex);
             return getValue(
-                context, mObject, &AttributeStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) {
-                    return (ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).*getter)();
+                context, mObject, &AttributeStat::setValue, mIndex, prop, [id, getter](const MWWorld::Ptr& ptr) {
+                    return (ptr.getClass().getCreatureStats(ptr).getAttribute(id).*getter)();
                 });
         }
 
@@ -213,9 +214,9 @@ namespace MWLua
 
         static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
         {
-            int index = std::get<int>(i);
+            auto id = static_cast<ESM::Attribute::AttributeID>(std::get<int>(i));
             auto& stats = ptr.getClass().getCreatureStats(ptr);
-            auto stat = stats.getAttribute(index);
+            auto stat = stats.getAttribute(id);
             float floatValue = LuaUtil::cast<float>(value);
             if (prop == "base")
                 stat.setBase(floatValue);
@@ -226,7 +227,7 @@ namespace MWLua
             }
             else if (prop == "modifier")
                 stat.setModifier(floatValue);
-            stats.setAttribute(index, stat);
+            stats.setAttribute(id, stat);
         }
     };
 
diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp
index 66ed8c7bab..64cc4e8597 100644
--- a/apps/openmw/mwmechanics/autocalcspell.cpp
+++ b/apps/openmw/mwmechanics/autocalcspell.cpp
@@ -27,13 +27,13 @@ namespace MWMechanics
         ESM::RefId mWeakestSpell;
     };
 
-    std::vector<ESM::RefId> autoCalcNpcSpells(
-        const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race)
+    std::vector<ESM::RefId> autoCalcNpcSpells(const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, const ESM::Race* race)
     {
         const MWWorld::Store<ESM::GameSetting>& gmst
             = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
         static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->mValue.getFloat();
-        float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence];
+        float baseMagicka = fNPCbaseMagickaMult * actorAttributes.at(ESM::Attribute::Intelligence).getBase();
 
         static const std::string schools[]
             = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
@@ -148,15 +148,15 @@ namespace MWMechanics
         return selectedSpells;
     }
 
-    std::vector<ESM::RefId> autoCalcPlayerSpells(
-        const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race)
+    std::vector<ESM::RefId> autoCalcPlayerSpells(const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, const ESM::Race* race)
     {
         const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
 
         static const float fPCbaseMagickaMult
             = esmStore.get<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
 
-        float baseMagicka = fPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence];
+        float baseMagicka = fPCbaseMagickaMult * actorAttributes.at(ESM::Attribute::Intelligence).getBase();
         bool reachedLimit = false;
         const ESM::Spell* weakestSpell = nullptr;
         int minCost = std::numeric_limits<int>::max();
@@ -227,8 +227,8 @@ namespace MWMechanics
         return selectedSpells;
     }
 
-    bool attrSkillCheck(
-        const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes)
+    bool attrSkillCheck(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes)
     {
         for (const auto& spellEffect : spell->mEffects.mList)
         {
@@ -250,8 +250,8 @@ namespace MWMechanics
 
             if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute))
             {
-                assert(spellEffect.mAttribute >= 0 && spellEffect.mAttribute < ESM::Attribute::Length);
-                if (actorAttributes[spellEffect.mAttribute] < iAutoSpellAttSkillMin)
+                auto found = actorAttributes.find(ESM::Attribute::AttributeID(spellEffect.mAttribute));
+                if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin)
                     return false;
             }
         }
@@ -313,7 +313,7 @@ namespace MWMechanics
     }
 
     float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
-        const int* actorAttributes, int effectiveSchool)
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, int effectiveSchool)
     {
         if (spell->mData.mType != ESM::Spell::ST_Spell)
             return 100.f;
@@ -334,7 +334,8 @@ namespace MWMechanics
                 spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this
 
         float castChance = skillTerm - MWMechanics::calcSpellCost(*spell)
-            + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck];
+            + 0.2f * actorAttributes.at(ESM::Attribute::Willpower).getBase()
+            + 0.1f * actorAttributes.at(ESM::Attribute::Luck).getBase();
         return castChance;
     }
 }
diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp
index 4c445b01ab..09efb34bdb 100644
--- a/apps/openmw/mwmechanics/autocalcspell.hpp
+++ b/apps/openmw/mwmechanics/autocalcspell.hpp
@@ -19,22 +19,22 @@ namespace MWMechanics
     /// Contains algorithm for calculating an NPC's spells based on stats
     /// @note We might want to move this code to a component later, so the editor can use it for preview purposes
 
-    std::vector<ESM::RefId> autoCalcNpcSpells(
-        const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race);
+    std::vector<ESM::RefId> autoCalcNpcSpells(const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, const ESM::Race* race);
 
-    std::vector<ESM::RefId> autoCalcPlayerSpells(
-        const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race);
+    std::vector<ESM::RefId> autoCalcPlayerSpells(const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, const ESM::Race* race);
 
     // Helpers
 
-    bool attrSkillCheck(
-        const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes);
+    bool attrSkillCheck(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes);
 
     void calcWeakestSchool(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
         int& effectiveSchool, float& skillTerm);
 
     float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
-        const int* actorAttributes, int effectiveSchool);
+        const std::map<ESM::Attribute::AttributeID, AttributeValue>& actorAttributes, int effectiveSchool);
 
 }
 
diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp
index 44e1f4d2e1..756ac1ef39 100644
--- a/apps/openmw/mwmechanics/creaturestats.cpp
+++ b/apps/openmw/mwmechanics/creaturestats.cpp
@@ -46,6 +46,10 @@ namespace MWMechanics
         , mLevel(0)
         , mAttackingOrSpell(false)
     {
+        for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get<ESM::Attribute>())
+        {
+            mAttributes.emplace(attribute.mId, AttributeValue{});
+        }
     }
 
     const AiSequence& CreatureStats::getAiSequence() const
@@ -74,13 +78,9 @@ namespace MWMechanics
         return fFatigueBase - fFatigueMult * (1 - normalised);
     }
 
-    const AttributeValue& CreatureStats::getAttribute(int index) const
+    const AttributeValue& CreatureStats::getAttribute(ESM::Attribute::AttributeID id) const
     {
-        if (index < 0 || index > 7)
-        {
-            throw std::runtime_error("attribute index is out of range");
-        }
-        return mAttributes[index];
+        return mAttributes.at(id);
     }
 
     const DynamicStat<float>& CreatureStats::getHealth() const
@@ -147,30 +147,25 @@ namespace MWMechanics
         return mMagicEffects;
     }
 
-    void CreatureStats::setAttribute(int index, float base)
+    void CreatureStats::setAttribute(ESM::Attribute::AttributeID id, float base)
     {
-        AttributeValue current = getAttribute(index);
+        AttributeValue current = getAttribute(id);
         current.setBase(base);
-        setAttribute(index, current);
+        setAttribute(id, current);
     }
 
-    void CreatureStats::setAttribute(int index, const AttributeValue& value)
+    void CreatureStats::setAttribute(ESM::Attribute::AttributeID id, const AttributeValue& value)
     {
-        if (index < 0 || index > 7)
-        {
-            throw std::runtime_error("attribute index is out of range");
-        }
-
-        const AttributeValue& currentValue = mAttributes[index];
+        const AttributeValue& currentValue = mAttributes.at(id);
 
         if (value != currentValue)
         {
-            mAttributes[index] = value;
+            mAttributes[id] = value;
 
-            if (index == ESM::Attribute::Intelligence)
+            if (id == ESM::Attribute::Intelligence)
                 recalculateMagicka();
-            else if (index == ESM::Attribute::Strength || index == ESM::Attribute::Willpower
-                || index == ESM::Attribute::Agility || index == ESM::Attribute::Endurance)
+            else if (id == ESM::Attribute::Strength || id == ESM::Attribute::Willpower || id == ESM::Attribute::Agility
+                || id == ESM::Attribute::Endurance)
             {
                 float strength = getAttribute(ESM::Attribute::Strength).getModified();
                 float willpower = getAttribute(ESM::Attribute::Willpower).getModified();
@@ -535,10 +530,10 @@ namespace MWMechanics
 
     void CreatureStats::writeState(ESM::CreatureStats& state) const
     {
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-            mAttributes[i].writeState(state.mAttributes[i]);
+        for (size_t i = 0; i < state.mAttributes.size(); ++i)
+            getAttribute(static_cast<ESM::Attribute::AttributeID>(i)).writeState(state.mAttributes[i]);
 
-        for (int i = 0; i < 3; ++i)
+        for (size_t i = 0; i < state.mDynamic.size(); ++i)
             mDynamic[i].writeState(state.mDynamic[i]);
 
         state.mTradeTime = mLastRestock.toEsm();
@@ -582,7 +577,7 @@ namespace MWMechanics
         state.mSummonGraveyard = mSummonGraveyard;
 
         state.mHasAiSettings = true;
-        for (int i = 0; i < 4; ++i)
+        for (size_t i = 0; i < state.mAiSettings.size(); ++i)
             mAiSettings[i].writeState(state.mAiSettings[i]);
 
         state.mMissingACDT = false;
@@ -592,10 +587,10 @@ namespace MWMechanics
     {
         if (!state.mMissingACDT)
         {
-            for (int i = 0; i < ESM::Attribute::Length; ++i)
-                mAttributes[i].readState(state.mAttributes[i]);
+            for (size_t i = 0; i < state.mAttributes.size(); ++i)
+                mAttributes[static_cast<ESM::Attribute::AttributeID>(i)].readState(state.mAttributes[i]);
 
-            for (int i = 0; i < 3; ++i)
+            for (size_t i = 0; i < state.mDynamic.size(); ++i)
                 mDynamic[i].readState(state.mDynamic[i]);
 
             mGoldPool = state.mGoldPool;
@@ -636,7 +631,7 @@ namespace MWMechanics
         mSummonGraveyard = state.mSummonGraveyard;
 
         if (state.mHasAiSettings)
-            for (int i = 0; i < 4; ++i)
+            for (size_t i = 0; i < state.mAiSettings.size(); ++i)
                 mAiSettings[i].readState(state.mAiSettings[i]);
         if (state.mRecalcDynamicStats)
             recalculateMagicka();
diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp
index 9d1830fe03..ffe6e40a91 100644
--- a/apps/openmw/mwmechanics/creaturestats.hpp
+++ b/apps/openmw/mwmechanics/creaturestats.hpp
@@ -40,7 +40,7 @@ namespace MWMechanics
     {
         static int sActorId;
         DrawState mDrawState;
-        AttributeValue mAttributes[ESM::Attribute::Length];
+        std::map<ESM::Attribute::AttributeID, AttributeValue> mAttributes;
         DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
         Spells mSpells;
         ActiveSpells mActiveSpells;
@@ -113,7 +113,7 @@ namespace MWMechanics
         /// @return total fall height
         float land(bool isPlayer = false);
 
-        const AttributeValue& getAttribute(int index) const;
+        const AttributeValue& getAttribute(ESM::Attribute::AttributeID id) const;
 
         const DynamicStat<float>& getHealth() const;
 
@@ -139,9 +139,9 @@ namespace MWMechanics
 
         MagicEffects& getMagicEffects();
 
-        void setAttribute(int index, const AttributeValue& value);
+        void setAttribute(ESM::Attribute::AttributeID id, const AttributeValue& value);
         // Shortcut to set only the base
-        void setAttribute(int index, float base);
+        void setAttribute(ESM::Attribute::AttributeID id, float base);
 
         void setHealth(const DynamicStat<float>& value);
 
@@ -293,6 +293,8 @@ namespace MWMechanics
 
         bool wasTeleported() const { return mTeleported; }
         void setTeleported(bool v) { mTeleported = v; }
+
+        const std::map<ESM::Attribute::AttributeID, AttributeValue> getAttributes() const { return mAttributes; }
     };
 }
 
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
index 30154785ea..c2cca5d5c1 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
@@ -148,11 +148,11 @@ namespace MWMechanics
 
             bool male = (player->mFlags & ESM::NPC::Female) == 0;
 
-            for (int i = 0; i < ESM::Attribute::Length; ++i)
+            for (const ESM::Attribute& attribute : esmStore.get<ESM::Attribute>())
             {
-                const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
+                const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[attribute.mId];
 
-                creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
+                creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale);
             }
 
             for (const ESM::Skill& skill : esmStore.get<ESM::Skill>())
@@ -195,7 +195,8 @@ namespace MWMechanics
             {
                 if (attribute >= 0 && attribute < ESM::Attribute::Length)
                 {
-                    creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10);
+                    auto id = static_cast<ESM::Attribute::AttributeID>(attribute);
+                    creatureStats.setAttribute(id, creatureStats.getAttribute(id).getBase() + 10);
                 }
             }
 
@@ -223,12 +224,10 @@ namespace MWMechanics
         if (mRaceSelected)
             race = esmStore.get<ESM::Race>().find(player->mRace);
 
-        int attributes[ESM::Attribute::Length];
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-            attributes[i] = npcStats.getAttribute(i).getBase();
         npcStats.updateHealth();
 
-        std::vector<ESM::RefId> selectedSpells = autoCalcPlayerSpells(npcStats.getSkills(), attributes, race);
+        std::vector<ESM::RefId> selectedSpells
+            = autoCalcPlayerSpells(npcStats.getSkills(), npcStats.getAttributes(), race);
 
         for (const ESM::RefId& spell : selectedSpells)
             creatureStats.getSpells().add(spell);
diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp
index 337757ebfa..7729432414 100644
--- a/apps/openmw/mwmechanics/npcstats.cpp
+++ b/apps/openmw/mwmechanics/npcstats.cpp
@@ -28,7 +28,6 @@ MWMechanics::NpcStats::NpcStats()
     , mTimeToStartDrowning(-1.0) // set breath to special value, it will be replaced during actor update
     , mIsWerewolf(false)
 {
-    mSkillIncreases.resize(ESM::Attribute::Length, 0);
     mSpecIncreases.resize(3, 0);
     for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
         mSkills.emplace(skill.mId, SkillValue{});
@@ -246,7 +245,7 @@ void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class
         }
     }
 
-    mSkillIncreases[skill->mData.mAttribute] += increase;
+    mSkillIncreases[ESM::Attribute::AttributeID(skill->mData.mAttribute)] += increase;
 
     mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger();
 
@@ -286,8 +285,7 @@ void MWMechanics::NpcStats::levelUp()
     mLevelProgress -= gmst.find("iLevelUpTotal")->mValue.getInteger();
     mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console
 
-    for (int i = 0; i < ESM::Attribute::Length; ++i)
-        mSkillIncreases[i] = 0;
+    mSkillIncreases.clear();
 
     const float endurance = getAttribute(ESM::Attribute::Endurance).getBase();
 
@@ -312,14 +310,12 @@ void MWMechanics::NpcStats::updateHealth()
     setHealth(floor(0.5f * (strength + endurance)));
 }
 
-int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
+int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const
 {
-    int num = mSkillIncreases[attribute];
-
-    if (num == 0)
+    auto it = mSkillIncreases.find(attribute);
+    if (it == mSkillIncreases.end() || it->second == 0)
         return 1;
-
-    num = std::min(10, num);
+    int num = std::min(10, it->second);
 
     // iLevelUp01Mult - iLevelUp10Mult
     std::stringstream gmst;
@@ -488,8 +484,9 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const
     state.mWerewolfKills = mWerewolfKills;
     state.mLevelProgress = mLevelProgress;
 
-    for (size_t i = 0; i < state.mSkillIncrease.size(); ++i)
-        state.mSkillIncrease[i] = mSkillIncreases[i];
+    state.mSkillIncrease.fill(0);
+    for (const auto& [key, value] : mSkillIncreases)
+        state.mSkillIncrease[key] = value;
 
     for (size_t i = 0; i < state.mSpecIncreases.size(); ++i)
         state.mSpecIncreases[i] = mSpecIncreases[i];
@@ -538,7 +535,7 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state)
     mLevelProgress = state.mLevelProgress;
 
     for (size_t i = 0; i < state.mSkillIncrease.size(); ++i)
-        mSkillIncreases[i] = state.mSkillIncrease[i];
+        mSkillIncreases[static_cast<ESM::Attribute::AttributeID>(i)] = state.mSkillIncrease[i];
 
     for (size_t i = 0; i < state.mSpecIncreases.size(); ++i)
         mSpecIncreases[i] = state.mSpecIncreases[i];
diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp
index 9026553bb5..39db2dd30a 100644
--- a/apps/openmw/mwmechanics/npcstats.hpp
+++ b/apps/openmw/mwmechanics/npcstats.hpp
@@ -36,7 +36,8 @@ namespace MWMechanics
         std::set<ESM::RefId> mExpelled;
         std::map<ESM::RefId, int> mFactionReputation;
         int mLevelProgress; // 0-10
-        std::vector<int> mSkillIncreases; // number of skill increases for each attribute (resets after leveling up)
+        std::map<ESM::Attribute::AttributeID, int>
+            mSkillIncreases; // number of skill increases for each attribute (resets after leveling up)
         std::vector<int> mSpecIncreases; // number of skill increases for each specialization (accumulates throughout
                                          // the entire game)
         std::set<ESM::RefId> mUsedIds;
@@ -87,7 +88,7 @@ namespace MWMechanics
 
         int getLevelProgress() const;
 
-        int getLevelupAttributeMultiplier(int attribute) const;
+        int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const;
 
         int getSkillIncreasesForSpecialization(int spec) const;
 
diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp
index b0120d91fb..2c410c842a 100644
--- a/apps/openmw/mwmechanics/spelleffects.cpp
+++ b/apps/openmw/mwmechanics/spelleffects.cpp
@@ -79,27 +79,30 @@ namespace
     void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
     {
         auto& creatureStats = target.getClass().getCreatureStats(target);
-        auto attr = creatureStats.getAttribute(effect.mArg);
+        auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
+        auto attr = creatureStats.getAttribute(attribute);
         if (effect.mEffectId == ESM::MagicEffect::DamageAttribute)
             magnitude = std::min(attr.getModified(), magnitude);
         attr.damage(magnitude);
-        creatureStats.setAttribute(effect.mArg, attr);
+        creatureStats.setAttribute(attribute, attr);
     }
 
     void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
     {
         auto& creatureStats = target.getClass().getCreatureStats(target);
-        auto attr = creatureStats.getAttribute(effect.mArg);
+        auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
+        auto attr = creatureStats.getAttribute(attribute);
         attr.restore(magnitude);
-        creatureStats.setAttribute(effect.mArg, attr);
+        creatureStats.setAttribute(attribute, attr);
     }
 
     void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
     {
         auto& creatureStats = target.getClass().getCreatureStats(target);
-        auto attr = creatureStats.getAttribute(effect.mArg);
+        auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
+        auto attr = creatureStats.getAttribute(attribute);
         attr.setModifier(attr.getModifier() + magnitude);
-        creatureStats.setAttribute(effect.mArg, attr);
+        creatureStats.setAttribute(attribute, attr);
     }
 
     void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
@@ -739,9 +742,10 @@ namespace MWMechanics
                 if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                 {
                     auto& creatureStats = target.getClass().getCreatureStats(target);
-                    AttributeValue attr = creatureStats.getAttribute(effect.mArg);
+                    auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
+                    AttributeValue attr = creatureStats.getAttribute(attribute);
                     attr.setBase(attr.getBase() + effect.mMagnitude);
-                    creatureStats.setAttribute(effect.mArg, attr);
+                    creatureStats.setAttribute(attribute, attr);
                 }
                 else
                     fortifyAttribute(target, effect, effect.mMagnitude);
@@ -1202,9 +1206,10 @@ namespace MWMechanics
                 if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                 {
                     auto& creatureStats = target.getClass().getCreatureStats(target);
-                    AttributeValue attr = creatureStats.getAttribute(effect.mArg);
+                    auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
+                    AttributeValue attr = creatureStats.getAttribute(attribute);
                     attr.setBase(attr.getBase() - effect.mMagnitude);
-                    creatureStats.setAttribute(effect.mArg, attr);
+                    creatureStats.setAttribute(attribute, attr);
                 }
                 else
                     fortifyAttribute(target, effect, -effect.mMagnitude);
diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp
index e2c0dad55f..9bd4495651 100644
--- a/apps/openmw/mwmechanics/spellpriority.cpp
+++ b/apps/openmw/mwmechanics/spellpriority.cpp
@@ -540,7 +540,11 @@ namespace MWMechanics
             case ESM::MagicEffect::DamageAttribute:
             case ESM::MagicEffect::DrainAttribute:
                 if (!enemy.isEmpty()
-                    && enemy.getClass().getCreatureStats(enemy).getAttribute(effect.mAttribute).getModified() <= 0)
+                    && enemy.getClass()
+                            .getCreatureStats(enemy)
+                            .getAttribute(ESM::Attribute::AttributeID(effect.mAttribute))
+                            .getModified()
+                        <= 0)
                     return 0.f;
                 {
                     if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length)
diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp
index 9e2e1ce798..f533926604 100644
--- a/apps/openmw/mwmechanics/spells.cpp
+++ b/apps/openmw/mwmechanics/spells.cpp
@@ -247,23 +247,24 @@ namespace MWMechanics
                 return;
 
             // Note: if target actor has the Restore attribute effects, stats will be restored.
-            for (std::vector<ESM::SpellState::PermanentSpellEffectInfo>::const_iterator effectIt = it->second.begin();
-                 effectIt != it->second.end(); ++effectIt)
+            for (const ESM::SpellState::PermanentSpellEffectInfo& info : it->second)
             {
                 // Applied corprus effects are already in loaded stats modifiers
-                if (effectIt->mId == ESM::MagicEffect::FortifyAttribute)
+                if (info.mId == ESM::MagicEffect::FortifyAttribute)
                 {
-                    AttributeValue attr = creatureStats->getAttribute(effectIt->mArg);
-                    attr.setModifier(attr.getModifier() - effectIt->mMagnitude);
-                    attr.damage(-effectIt->mMagnitude);
-                    creatureStats->setAttribute(effectIt->mArg, attr);
+                    auto id = static_cast<ESM::Attribute::AttributeID>(info.mArg);
+                    AttributeValue attr = creatureStats->getAttribute(id);
+                    attr.setModifier(attr.getModifier() - info.mMagnitude);
+                    attr.damage(-info.mMagnitude);
+                    creatureStats->setAttribute(id, attr);
                 }
-                else if (effectIt->mId == ESM::MagicEffect::DrainAttribute)
+                else if (info.mId == ESM::MagicEffect::DrainAttribute)
                 {
-                    AttributeValue attr = creatureStats->getAttribute(effectIt->mArg);
-                    attr.setModifier(attr.getModifier() + effectIt->mMagnitude);
-                    attr.damage(effectIt->mMagnitude);
-                    creatureStats->setAttribute(effectIt->mArg, attr);
+                    auto id = static_cast<ESM::Attribute::AttributeID>(info.mArg);
+                    AttributeValue attr = creatureStats->getAttribute(id);
+                    attr.setModifier(attr.getModifier() + info.mMagnitude);
+                    attr.damage(info.mMagnitude);
+                    creatureStats->setAttribute(id, attr);
                 }
             }
         }
diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp
index 23c1083903..222820f3bf 100644
--- a/apps/openmw/mwscript/statsextensions.cpp
+++ b/apps/openmw/mwscript/statsextensions.cpp
@@ -109,10 +109,10 @@ namespace MWScript
         template <class R>
         class OpGetAttribute : public Interpreter::Opcode0
         {
-            int mIndex;
+            ESM::Attribute::AttributeID mIndex;
 
         public:
-            OpGetAttribute(int index)
+            OpGetAttribute(ESM::Attribute::AttributeID index)
                 : mIndex(index)
             {
             }
@@ -130,10 +130,10 @@ namespace MWScript
         template <class R>
         class OpSetAttribute : public Interpreter::Opcode0
         {
-            int mIndex;
+            ESM::Attribute::AttributeID mIndex;
 
         public:
-            OpSetAttribute(int index)
+            OpSetAttribute(ESM::Attribute::AttributeID index)
                 : mIndex(index)
             {
             }
@@ -154,10 +154,10 @@ namespace MWScript
         template <class R>
         class OpModAttribute : public Interpreter::Opcode0
         {
-            int mIndex;
+            ESM::Attribute::AttributeID mIndex;
 
         public:
-            OpModAttribute(int index)
+            OpModAttribute(ESM::Attribute::AttributeID index)
                 : mIndex(index)
             {
             }
@@ -1322,17 +1322,18 @@ namespace MWScript
         {
             for (int i = 0; i < Compiler::Stats::numberOfAttributes; ++i)
             {
-                interpreter.installSegment5<OpGetAttribute<ImplicitRef>>(Compiler::Stats::opcodeGetAttribute + i, i);
+                auto id = static_cast<ESM::Attribute::AttributeID>(i);
+                interpreter.installSegment5<OpGetAttribute<ImplicitRef>>(Compiler::Stats::opcodeGetAttribute + i, id);
                 interpreter.installSegment5<OpGetAttribute<ExplicitRef>>(
-                    Compiler::Stats::opcodeGetAttributeExplicit + i, i);
+                    Compiler::Stats::opcodeGetAttributeExplicit + i, id);
 
-                interpreter.installSegment5<OpSetAttribute<ImplicitRef>>(Compiler::Stats::opcodeSetAttribute + i, i);
+                interpreter.installSegment5<OpSetAttribute<ImplicitRef>>(Compiler::Stats::opcodeSetAttribute + i, id);
                 interpreter.installSegment5<OpSetAttribute<ExplicitRef>>(
-                    Compiler::Stats::opcodeSetAttributeExplicit + i, i);
+                    Compiler::Stats::opcodeSetAttributeExplicit + i, id);
 
-                interpreter.installSegment5<OpModAttribute<ImplicitRef>>(Compiler::Stats::opcodeModAttribute + i, i);
+                interpreter.installSegment5<OpModAttribute<ImplicitRef>>(Compiler::Stats::opcodeModAttribute + i, id);
                 interpreter.installSegment5<OpModAttribute<ExplicitRef>>(
-                    Compiler::Stats::opcodeModAttributeExplicit + i, i);
+                    Compiler::Stats::opcodeModAttributeExplicit + i, id);
             }
 
             for (int i = 0; i < Compiler::Stats::numberOfDynamics; ++i)
diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp
index d3e6db10a7..51c902a8f6 100644
--- a/apps/openmw/mwworld/magiceffects.cpp
+++ b/apps/openmw/mwworld/magiceffects.cpp
@@ -43,8 +43,7 @@ namespace MWWorld
 
             ESM::CreatureStats::CorprusStats stats;
             stats.mNextWorsening = oldStats.mNextWorsening;
-            for (int i = 0; i < ESM::Attribute::Length; ++i)
-                stats.mWorsenings[i] = 0;
+            stats.mWorsenings.fill(0);
 
             for (auto& effect : spell->mEffects.mList)
             {
@@ -179,8 +178,8 @@ namespace MWWorld
             {
                 it->mNextWorsening = spell.second.mNextWorsening;
                 int worsenings = 0;
-                for (int i = 0; i < ESM::Attribute::Length; ++i)
-                    worsenings = std::max(spell.second.mWorsenings[i], worsenings);
+                for (const auto& worsening : spell.second.mWorsenings)
+                    worsenings = std::max(worsening, worsenings);
                 it->mWorsenings = worsenings;
             }
         }
@@ -209,20 +208,19 @@ namespace MWWorld
             }
         }
         // Reset modifiers that were previously recalculated each frame
-        for (std::size_t i = 0; i < ESM::Attribute::Length; ++i)
-            creatureStats.mAttributes[i].mMod = 0.f;
-        for (std::size_t i = 0; i < 3; ++i)
+        for (auto& attribute : creatureStats.mAttributes)
+            attribute.mMod = 0.f;
+        for (auto& dynamic : creatureStats.mDynamic)
         {
-            auto& dynamic = creatureStats.mDynamic[i];
             dynamic.mCurrent -= dynamic.mMod - dynamic.mBase;
             dynamic.mMod = 0.f;
         }
-        for (std::size_t i = 0; i < 4; ++i)
-            creatureStats.mAiSettings[i].mMod = 0.f;
+        for (auto& setting : creatureStats.mAiSettings)
+            setting.mMod = 0.f;
         if (npcStats)
         {
-            for (std::size_t i = 0; i < npcStats->mSkills.size(); ++i)
-                npcStats->mSkills[i].mMod = 0.f;
+            for (auto& skill : npcStats->mSkills)
+                skill.mMod = 0.f;
         }
     }
 
@@ -230,9 +228,9 @@ namespace MWWorld
     // version or not
     void convertStats(ESM::CreatureStats& creatureStats)
     {
-        for (std::size_t i = 0; i < 3; ++i)
-            creatureStats.mDynamic[i].mMod = 0.f;
-        for (std::size_t i = 0; i < 4; ++i)
-            creatureStats.mAiSettings[i].mMod = 0.f;
+        for (auto& dynamic : creatureStats.mDynamic)
+            dynamic.mMod = 0.f;
+        for (auto& setting : creatureStats.mAiSettings)
+            setting.mMod = 0.f;
     }
 }
diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp
index bb70c46aa0..ad0cc92ad8 100644
--- a/apps/openmw/mwworld/player.cpp
+++ b/apps/openmw/mwworld/player.cpp
@@ -61,8 +61,8 @@ namespace MWWorld
 
         for (size_t i = 0; i < mSaveSkills.size(); ++i)
             mSaveSkills[i] = stats.getSkill(ESM::Skill::indexToRefId(i)).getModified();
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-            mSaveAttributes[i] = stats.getAttribute(i).getModified();
+        for (size_t i = 0; i < mSaveAttributes.size(); ++i)
+            mSaveAttributes[i] = stats.getAttribute(static_cast<ESM::Attribute::AttributeID>(i)).getModified();
     }
 
     void Player::restoreStats()
@@ -79,12 +79,13 @@ namespace MWWorld
             skill.restore(skill.getDamage());
             skill.setModifier(mSaveSkills[i] - skill.getBase());
         }
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
+        for (size_t i = 0; i < mSaveAttributes.size(); ++i)
         {
-            auto attribute = npcStats.getAttribute(i);
+            auto id = static_cast<ESM::Attribute::AttributeID>(i);
+            auto attribute = npcStats.getAttribute(id);
             attribute.restore(attribute.getDamage());
             attribute.setModifier(mSaveAttributes[i] - attribute.getBase());
-            npcStats.setAttribute(i, attribute);
+            npcStats.setAttribute(id, attribute);
         }
     }
 
@@ -252,11 +253,7 @@ namespace MWWorld
         mLastKnownExteriorPosition = osg::Vec3f(0, 0, 0);
 
         mSaveSkills.fill(0.f);
-
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
-        {
-            mSaveAttributes[i] = 0.f;
-        }
+        mSaveAttributes.fill(0.f);
 
         mMarkedPosition.pos[0] = 0;
         mMarkedPosition.pos[1] = 0;
@@ -291,7 +288,7 @@ namespace MWWorld
         else
             player.mHasMark = false;
 
-        for (int i = 0; i < ESM::Attribute::Length; ++i)
+        for (size_t i = 0; i < mSaveAttributes.size(); ++i)
             player.mSaveAttributes[i] = mSaveAttributes[i];
         for (size_t i = 0; i < mSaveSkills.size(); ++i)
             player.mSaveSkills[i] = mSaveSkills[i];
@@ -329,7 +326,7 @@ namespace MWWorld
 
             mPlayer.load(player.mObject);
 
-            for (int i = 0; i < ESM::Attribute::Length; ++i)
+            for (size_t i = 0; i < mSaveAttributes.size(); ++i)
                 mSaveAttributes[i] = player.mSaveAttributes[i];
             for (size_t i = 0; i < mSaveSkills.size(); ++i)
                 mSaveSkills[i] = player.mSaveSkills[i];
diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp
index 12eadab10f..0d56833df9 100644
--- a/apps/openmw/mwworld/player.hpp
+++ b/apps/openmw/mwworld/player.hpp
@@ -52,7 +52,7 @@ namespace MWWorld
 
         // Saved stats prior to becoming a werewolf
         std::array<float, ESM::Skill::Length> mSaveSkills;
-        float mSaveAttributes[ESM::Attribute::Length];
+        std::array<float, ESM::Attribute::Length> mSaveAttributes;
 
         bool mJumping;
 
diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp
index 8a7625b9f0..9121bcb8f5 100644
--- a/components/esm3/creaturestats.cpp
+++ b/components/esm3/creaturestats.cpp
@@ -10,11 +10,11 @@ namespace ESM
     void CreatureStats::load(ESMReader& esm)
     {
         const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion;
-        for (int i = 0; i < 8; ++i)
-            mAttributes[i].load(esm, intFallback);
+        for (auto& attribute : mAttributes)
+            attribute.load(esm, intFallback);
 
-        for (int i = 0; i < 3; ++i)
-            mDynamic[i].load(esm);
+        for (auto& dynamic : mDynamic)
+            dynamic.load(esm);
 
         mGoldPool = 0;
         esm.getHNOT(mGoldPool, "GOLD");
@@ -154,8 +154,8 @@ namespace ESM
 
         if (mHasAiSettings)
         {
-            for (int i = 0; i < 4; ++i)
-                mAiSettings[i].load(esm);
+            for (auto& setting : mAiSettings)
+                setting.load(esm);
         }
 
         while (esm.isNextSub("CORP"))
@@ -179,11 +179,11 @@ namespace ESM
 
     void CreatureStats::save(ESMWriter& esm) const
     {
-        for (int i = 0; i < 8; ++i)
-            mAttributes[i].save(esm);
+        for (const auto& attribute : mAttributes)
+            attribute.save(esm);
 
-        for (int i = 0; i < 3; ++i)
-            mDynamic[i].save(esm);
+        for (const auto& dynamic : mDynamic)
+            dynamic.save(esm);
 
         if (mGoldPool)
             esm.writeHNT("GOLD", mGoldPool);
@@ -268,8 +268,8 @@ namespace ESM
         esm.writeHNT("AISE", mHasAiSettings);
         if (mHasAiSettings)
         {
-            for (int i = 0; i < 4; ++i)
-                mAiSettings[i].save(esm);
+            for (const auto& setting : mAiSettings)
+                setting.save(esm);
         }
         if (mMissingACDT)
             esm.writeHNT("NOAC", mMissingACDT);
diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp
index 1a9f087e39..d9df6200af 100644
--- a/components/esm3/creaturestats.hpp
+++ b/components/esm3/creaturestats.hpp
@@ -1,6 +1,7 @@
 #ifndef OPENMW_ESM_CREATURESTATS_H
 #define OPENMW_ESM_CREATURESTATS_H
 
+#include <array>
 #include <map>
 #include <string>
 #include <vector>
@@ -26,19 +27,19 @@ namespace ESM
     {
         struct CorprusStats
         {
-            int mWorsenings[Attribute::Length];
+            std::array<int, Attribute::Length> mWorsenings;
             TimeStamp mNextWorsening;
         };
 
-        StatState<float> mAttributes[Attribute::Length];
-        StatState<float> mDynamic[3];
+        std::array<StatState<float>, Attribute::Length> mAttributes;
+        std::array<StatState<float>, 3> mDynamic;
 
         MagicEffects mMagicEffects;
 
         AiSequence::AiSequence mAiSequence;
 
         bool mHasAiSettings;
-        StatState<int> mAiSettings[4];
+        std::array<StatState<int>, 4> mAiSettings;
 
         std::map<SummonKey, int> mSummonedCreatureMap;
         std::multimap<int, int> mSummonedCreatures;
diff --git a/files/data/mygui/openmw_chargen_review.layout b/files/data/mygui/openmw_chargen_review.layout
index 1f158dc064..5f6034bf43 100644
--- a/files/data/mygui/openmw_chargen_review.layout
+++ b/files/data/mygui/openmw_chargen_review.layout
@@ -46,64 +46,7 @@
         </Widget>
 
         <!-- Player attributes -->
-        <Widget type="Widget" skin="MW_Box" position="8 224 265 156">
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 4 250 18" name="Attribute0">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sStrDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_strength.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 22 250 18" name="Attribute1">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sIntDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_int.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 40 250 18" name="Attribute2">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sWilDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_wilpower.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 58 250 18" name="Attribute3">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sAgiDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_agility.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 76 250 18" name="Attribute4">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sSpdDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_speed.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 94 250 18" name="Attribute5">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sEndDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_endurance.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 112 250 18" name="Attribute6">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sPerDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_personality.dds"/>
-            </Widget>
-            <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 130 250 18" name="Attribute7">
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
-            </Widget>
-        </Widget>
+        <Widget type="Widget" skin="MW_Box" position="8 224 265 156" name="Attributes" />
 
         <!-- Player Skills -->
         <Widget type="Widget" skin="MW_Box" position="281 7 244 372" align="Left VStretch" name="Skills">
diff --git a/files/data/mygui/openmw_chargen_select_attribute.layout b/files/data/mygui/openmw_chargen_select_attribute.layout
index 621ba90817..7e475291f9 100644
--- a/files/data/mygui/openmw_chargen_select_attribute.layout
+++ b/files/data/mygui/openmw_chargen_select_attribute.layout
@@ -10,14 +10,9 @@
             </Widget>
 
             <!-- Attribute list -->
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 28 216 18" name="Attribute0" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 46 216 18" name="Attribute1" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 64 216 18" name="Attribute2" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 82 216 18" name="Attribute3" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 100 216 18" name="Attribute4" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 118 216 18" name="Attribute5" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 136 216 18" name="Attribute6" align="Left Top"/>
-            <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 154 216 18" name="Attribute7" align="Left Top"/>
+            <Widget type="ScrollView" skin="MW_ScrollView" position="0 28 216 144" name="Attributes" align="Left Top">
+                <Property key="CanvasAlign" value="Left"/>
+            </Widget>
 
             <!-- Dialog buttons -->
             <Widget type="HBox" position="0 175 216 28">
diff --git a/files/data/mygui/openmw_levelup_dialog.layout b/files/data/mygui/openmw_levelup_dialog.layout
index ef161d7f58..498fa97c80 100644
--- a/files/data/mygui/openmw_levelup_dialog.layout
+++ b/files/data/mygui/openmw_levelup_dialog.layout
@@ -27,131 +27,10 @@
             <UserString key="VStretch" value="false"/>
         </Widget>
 
-        <Widget type="Widget" skin="" position="0 280 420 84" name="AssignWidget">
+        <Widget type="ScrollView" skin="MW_ScrollView" position="0 280 420 84" name="AssignWidget">
             <UserString key="HStretch" value="false"/>
             <UserString key="VStretch" value="false"/>
-
-            <Widget type="TextBox" skin="SandTextVCenter" position="32 0 100 20" name="AttribMultiplier1"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="32 20 100 20" name="AttribMultiplier2"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="32 40 100 20" name="AttribMultiplier3"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="32 60 100 20" name="AttribMultiplier4"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="218 0 100 20" name="AttribMultiplier5"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="218 20 100 20" name="AttribMultiplier6"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="218 40 100 20" name="AttribMultiplier7"/>
-            <Widget type="TextBox" skin="SandTextVCenter" position="218 60 100 20" name="AttribMultiplier8"/>
-
-            <Widget type="HBox" position="52 0 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib1">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sStrDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_strength.dds"/>
-                    <Property key="Caption" value="#{sAttributeStrength}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal1">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="52 20 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib2">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sIntDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_int.dds"/>
-                    <Property key="Caption" value="#{sAttributeIntelligence}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal2">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="52 40 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib3">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sWilDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_wilpower.dds"/>
-                    <Property key="Caption" value="#{sAttributeWillpower}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal3">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="52 60 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib4">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sAgiDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_agility.dds"/>
-                    <Property key="Caption" value="#{sAttributeAgility}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal4">
-                </Widget>
-            </Widget>
-
-
-            <Widget type="HBox" position="238 0 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib5">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sSpdDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_speed.dds"/>
-                    <Property key="Caption" value="#{sAttributeSpeed}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal5">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="238 20 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib6">
-                    <UserString key="TextPadding" value="0 0"/>
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sEndDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_endurance.dds"/>
-                    <Property key="Caption" value="#{sAttributeEndurance}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal6">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="238 40 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib7">
-                <UserString key="TextPadding" value="0 0"/>
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sPerDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_personality.dds"/>
-                    <Property key="Caption" value="#{sAttributePersonality}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal7">
-                </Widget>
-            </Widget>
-
-            <Widget type="HBox" position="238 60 200 20">
-                <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib8">
-                <UserString key="TextPadding" value="0 0"/>
-                <UserString key="ToolTipType" value="Layout"/>
-                <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
-                <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
-                <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
-                    <Property key="Caption" value="#{sAttributeLuck}"/>
-                </Widget>
-                <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal8">
-                </Widget>
-            </Widget>
+            <Property key="CanvasAlign" value="Left"/>
         </Widget>
 
         <Widget type="HBox" skin="" position="0 0 330 24">
diff --git a/files/data/mygui/openmw_stats_window.layout b/files/data/mygui/openmw_stats_window.layout
index 77de3ac753..93a648f0e0 100644
--- a/files/data/mygui/openmw_stats_window.layout
+++ b/files/data/mygui/openmw_stats_window.layout
@@ -113,120 +113,8 @@
             </Widget>
 
             <Widget type="Widget" skin="MW_Box" position="8 148 212 152" align="Left Top Stretch">
-            <!-- TODO: this should be a scroll view -->
-            <Widget type="Widget" skin="" position="4 4 204 144" align="Left Top Stretch">
-                <Widget type="Button" skin="" position="0 0 204 18" name="Attrib1Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sStrDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_strength.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib1" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal1" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 18 204 18" name="Attrib2Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sIntDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_int.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib2" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal2" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 36 204 18" name="Attrib3Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sWilDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_wilpower.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib3" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal3" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 54 204 18" name="Attrib4Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sAgiDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_agility.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib4" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal4" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 72 204 18" name="Attrib5Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sSpdDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_speed.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib5" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal5" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 90 204 18" name="Attrib6Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sEndDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_endurance.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib6" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal6" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 108 204 18" name="Attrib7Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sPerDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_personality.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib7" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal7" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-
-                <Widget type="Button" skin="" position="0 126 204 18" name="Attrib8Box" align="Left Top HStretch">
-                    <UserString key="ToolTipType" value="Layout"/>
-                    <UserString key="ToolTipLayout" value="AttributeToolTip"/>
-                    <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
-                    <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
-                    <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
-                    <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib8" align="Left Top HStretch">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                    <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal8" align="Right Top">
-                        <Property key="NeedMouse" value="false"/>
-                    </Widget>
-                </Widget>
-            </Widget>
+                <!-- TODO: this should be a scroll view -->
+                <Widget type="Widget" skin="" position="4 4 204 144" align="Left Top Stretch" name="AttributeView" />
             </Widget>
 
         </Widget>