diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml
index 85bc5d8..02a1994 100644
--- a/.github/workflows/arch.yml
+++ b/.github/workflows/arch.yml
@@ -2,39 +2,6 @@ name: Build & Test (Arch)
 
 on: [push, pull_request, workflow_dispatch]
 jobs:
-  gcc:
-    name: "gcc build / clang test"
-    runs-on: ubuntu-latest
-    container:
-      image: archlinux
-    steps:
-      - name: Checkout repository actions
-        uses: actions/checkout@v4
-        with:
-          sparse-checkout: .github/actions
-
-      - name: Get required pkgs
-        run: |
-          sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf
-          pacman --noconfirm --noprogressbar -Syyu
-          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
-
-      - name: Build hyprlang with gcc
-        run: |
-          CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
-          CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target hyprlang -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
-          cmake --install ./build
-
-      - name: Build tests with clang
-        run: |
-          rm -rf ./build
-          CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-stdlib=libc++" -S . -B ./build
-          CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target tests -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
-
-      - name: Run tests
-        run: |
-          cd ./build && ctest --output-on-failure
-
   asan:
     name: "gcc build / ASan tests"
     runs-on: ubuntu-latest
@@ -50,7 +17,11 @@ jobs:
         run: |
           sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf
           pacman --noconfirm --noprogressbar -Syyu
-          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang
+          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git
+
+      - name: Get hyprutils-git
+        run: |
+          git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
 
       - name: Build with gcc
         run: |
@@ -77,7 +48,11 @@ jobs:
         run: |
           sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf
           pacman --noconfirm --noprogressbar -Syyu
-          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang
+          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git
+
+      - name: Get hyprutils-git
+        run: |
+          git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
 
       - name: Build with gcc
         run: |
@@ -104,7 +79,11 @@ jobs:
         run: |
           sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf
           pacman --noconfirm --noprogressbar -Syyu
-          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang
+          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git
+
+      - name: Get hyprutils-git
+        run: |
+          git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
 
       - name: Build with gcc
         run: |
@@ -131,7 +110,11 @@ jobs:
         run: |
           sed -i 's/SigLevel    = Required DatabaseOptional/SigLevel    = Optional TrustAll/' /etc/pacman.conf
           pacman --noconfirm --noprogressbar -Syyu
-          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
+          pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ git
+
+      - name: Get hyprutils-git
+        run: |
+          git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
 
       - name: Build hyprlang with clang
         run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f64c315..46f8513 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,6 +28,11 @@ add_compile_definitions(HYPRLANG_INTERNAL)
 
 set(CMAKE_CXX_STANDARD 23)
 
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(deps REQUIRED IMPORTED_TARGET
+    hyprutils>=0.1.1
+)
+
 file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/hyprlang.hpp")
 
 add_library(hyprlang SHARED ${SRCFILES})
@@ -40,6 +45,8 @@ set_target_properties(hyprlang PROPERTIES
     SOVERSION 2
     PUBLIC_HEADER include/hyprlang.hpp)
 
+target_link_libraries(hyprlang PkgConfig::deps)
+
 if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
     # for std::expected.
     # probably evil. Arch's clang is very outdated tho...
@@ -55,12 +62,12 @@ install(TARGETS hyprlang)
 add_custom_target(tests)
 
 add_executable(hyprlang_test "tests/parse/main.cpp")
-target_link_libraries(hyprlang_test PRIVATE hyprlang)
+target_link_libraries(hyprlang_test PRIVATE hyprlang hyprutils)
 add_test(NAME "Parsing" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprlang_test "parse")
 add_dependencies(tests hyprlang_test)
 
 add_executable(hyprlang_fuzz "tests/fuzz/main.cpp")
-target_link_libraries(hyprlang_fuzz PRIVATE hyprlang)
+target_link_libraries(hyprlang_fuzz PRIVATE hyprlang hyprutils)
 add_test(NAME "Fuzz" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprlang_fuzz "fuzz")
 add_dependencies(tests hyprlang_fuzz)
 
diff --git a/src/VarList.cpp b/src/VarList.cpp
deleted file mode 100644
index 518acde..0000000
--- a/src/VarList.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#include "VarList.hpp"
-#include <ranges>
-#include <algorithm>
-
-static std::string removeBeginEndSpacesTabs(std::string str) {
-    if (str.empty())
-        return str;
-
-    int countBefore = 0;
-    while (str[countBefore] == ' ' || str[countBefore] == '\t') {
-        countBefore++;
-    }
-
-    int countAfter = 0;
-    while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
-        countAfter++;
-    }
-
-    str = str.substr(countBefore, str.length() - countBefore - countAfter);
-
-    return str;
-}
-
-CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) {
-    if (in.empty())
-        m_vArgs.emplace_back("");
-
-    std::string args{in};
-    size_t      idx = 0;
-    size_t      pos = 0;
-    std::ranges::replace_if(
-        args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
-
-    for (const auto& s : args | std::views::split(0)) {
-        if (removeEmpty && s.empty())
-            continue;
-        if (++idx == lastArgNo) {
-            m_vArgs.emplace_back(removeBeginEndSpacesTabs(in.substr(pos)));
-            break;
-        }
-        pos += s.size() + 1;
-        m_vArgs.emplace_back(removeBeginEndSpacesTabs(std::string_view{s}.data()));
-    }
-}
-
-std::string CVarList::join(const std::string& joiner, size_t from, size_t to) const {
-    size_t      last = to == 0 ? size() : to;
-
-    std::string rolling;
-    for (size_t i = from; i < last; ++i) {
-        rolling += m_vArgs[i] + (i + 1 < last ? joiner : "");
-    }
-
-    return rolling;
-}
\ No newline at end of file
diff --git a/src/VarList.hpp b/src/VarList.hpp
deleted file mode 100644
index 1374da6..0000000
--- a/src/VarList.hpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#pragma once
-#include <functional>
-#include <vector>
-#include <string>
-
-class CVarList {
-  public:
-    /** Split string into arg list
-        @param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args
-        @param delim if delimiter is 's', use std::isspace
-        @param removeEmpty remove empty args from argv
-    */
-    CVarList(const std::string& in, const size_t maxSize = 0, const char delim = ',', const bool removeEmpty = false);
-
-    ~CVarList() = default;
-
-    size_t size() const {
-        return m_vArgs.size();
-    }
-
-    std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
-
-    void        map(std::function<void(std::string&)> func) {
-        for (auto& s : m_vArgs)
-            func(s);
-    }
-
-    void append(const std::string arg) {
-        m_vArgs.emplace_back(arg);
-    }
-
-    std::string operator[](const size_t& idx) const {
-        if (idx >= m_vArgs.size())
-            return "";
-        return m_vArgs[idx];
-    }
-
-    // for range-based loops
-    std::vector<std::string>::iterator begin() {
-        return m_vArgs.begin();
-    }
-    std::vector<std::string>::const_iterator begin() const {
-        return m_vArgs.begin();
-    }
-    std::vector<std::string>::iterator end() {
-        return m_vArgs.end();
-    }
-    std::vector<std::string>::const_iterator end() const {
-        return m_vArgs.end();
-    }
-
-    bool contains(const std::string& el) {
-        for (auto& a : m_vArgs) {
-            if (a == el)
-                return true;
-        }
-
-        return false;
-    }
-
-  private:
-    std::vector<std::string> m_vArgs;
-};
\ No newline at end of file
diff --git a/src/config.cpp b/src/config.cpp
index 5f02be7..a295650 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -8,10 +8,11 @@
 #include <expected>
 #include <sstream>
 #include <cstring>
-
-#include "VarList.hpp"
+#include <hyprutils/string/VarList.hpp>
+#include <hyprutils/string/String.hpp>
 
 using namespace Hyprlang;
+using namespace Hyprutils::String;
 
 #ifdef __APPLE__
 #include <crt_externs.h>
@@ -33,25 +34,6 @@ static size_t seekABIStructSize(const void* begin, size_t startOffset, size_t ma
     return 0;
 }
 
-static std::string removeBeginEndSpacesTabs(std::string str) {
-    if (str.empty())
-        return str;
-
-    int countBefore = 0;
-    while (str[countBefore] == ' ' || str[countBefore] == '\t') {
-        countBefore++;
-    }
-
-    int countAfter = 0;
-    while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
-        countAfter++;
-    }
-
-    str = str.substr(countBefore, str.length() - countBefore - countAfter);
-
-    return str;
-}
-
 CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) {
     SConfigOptions options;
     std::memcpy(&options, &options_, seekABIStructSize(&options_, 16, sizeof(SConfigOptions)));
@@ -177,62 +159,27 @@ void CConfig::commence() {
     }
 }
 
-static bool isNumber(const std::string& str, bool allowfloat) {
-
-    std::string copy = str;
-    if (*copy.begin() == '-')
-        copy = copy.substr(1);
-
-    if (copy.empty())
-        return false;
-
-    bool point = !allowfloat;
-    for (auto& c : copy) {
-        if (c == '.') {
-            if (point)
-                return false;
-            point = true;
-            continue;
-        }
-
-        if (!std::isdigit(c))
-            return false;
-    }
-
-    return true;
-}
-
-static void replaceAll(std::string& str, const std::string& from, const std::string& to) {
-    if (from.empty())
-        return;
-    size_t pos = 0;
-    while ((pos = str.find(from, pos)) != std::string::npos) {
-        str.replace(pos, from.length(), to);
-        pos += to.length();
-    }
-}
-
 static std::expected<int64_t, std::string> configStringToInt(const std::string& VALUE) {
     if (VALUE.starts_with("0x")) {
         // Values with 0x are hex
         const auto VALUEWITHOUTHEX = VALUE.substr(2);
         return stoll(VALUEWITHOUTHEX, nullptr, 16);
     } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
-        const auto VALUEWITHOUTFUNC = removeBeginEndSpacesTabs(VALUE.substr(5, VALUE.length() - 6));
+        const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6));
 
         // try doing it the comma way first
         if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 3) {
             // cool
             std::string rolling = VALUEWITHOUTFUNC;
-            auto        r       = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto        r       = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
             rolling             = rolling.substr(rolling.find(',') + 1);
-            auto g              = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto g              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
             rolling             = rolling.substr(rolling.find(',') + 1);
-            auto b              = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto b              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
             rolling             = rolling.substr(rolling.find(',') + 1);
             uint8_t a           = 0;
             try {
-                a = std::round(std::stof(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(',')))) * 255.f);
+                a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f);
             } catch (std::exception& e) { return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); }
 
             if (!r.has_value() || !g.has_value() || !b.has_value())
@@ -249,17 +196,17 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
         return std::unexpected("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values");
 
     } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
-        const auto VALUEWITHOUTFUNC = removeBeginEndSpacesTabs(VALUE.substr(4, VALUE.length() - 5));
+        const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5));
 
         // try doing it the comma way first
         if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 2) {
             // cool
             std::string rolling = VALUEWITHOUTFUNC;
-            auto        r       = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto        r       = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
             rolling             = rolling.substr(rolling.find(',') + 1);
-            auto g              = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto g              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
             rolling             = rolling.substr(rolling.find(',') + 1);
-            auto b              = configStringToInt(removeBeginEndSpacesTabs(rolling.substr(0, rolling.find(','))));
+            auto b              = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
 
             if (!r.has_value() || !g.has_value() || !b.has_value())
                 return std::unexpected("failed parsing " + VALUEWITHOUTFUNC);
@@ -513,7 +460,7 @@ CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& r
 }
 
 void CConfigImpl::parseComment(const std::string& comment) {
-    const auto COMMENT = removeBeginEndSpacesTabs(comment);
+    const auto COMMENT = trim(comment);
 
     if (!COMMENT.starts_with("hyprlang"))
         return;
@@ -527,7 +474,7 @@ void CConfigImpl::parseComment(const std::string& comment) {
 CParseResult CConfig::parseLine(std::string line, bool dynamic) {
     CParseResult result;
 
-    line = removeBeginEndSpacesTabs(line);
+    line = trim(line);
 
     auto commentPos = line.find('#');
 
@@ -556,7 +503,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
         }
     }
 
-    line = removeBeginEndSpacesTabs(line);
+    line = trim(line);
 
     if (line.empty())
         return result;
@@ -572,8 +519,8 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
     if (equalsPos != std::string::npos) {
         // set value or call handler
         CParseResult ret;
-        auto         LHS = removeBeginEndSpacesTabs(line.substr(0, equalsPos));
-        auto         RHS = removeBeginEndSpacesTabs(line.substr(equalsPos + 1));
+        auto         LHS = trim(line.substr(0, equalsPos));
+        auto         RHS = trim(line.substr(equalsPos + 1));
 
         if (LHS.empty()) {
             result.setError("Empty lhs.");
@@ -591,9 +538,9 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
                 const auto RHSIT = RHS.find("$" + var.name);
 
                 if (LHSIT != std::string::npos)
-                    replaceAll(LHS, "$" + var.name, var.value);
+                    replaceInString(LHS, "$" + var.name, var.value);
                 if (RHSIT != std::string::npos)
-                    replaceAll(RHS, "$" + var.name, var.value);
+                    replaceInString(RHS, "$" + var.name, var.value);
 
                 if (RHSIT == std::string::npos && LHSIT == std::string::npos)
                     continue;
@@ -657,7 +604,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
             }
 
             line.pop_back();
-            line = removeBeginEndSpacesTabs(line);
+            line = trim(line);
             impl->categories.push_back(line);
         }
     }