diff --git a/app/Auth/Queries/AllRolesPaginatedAndSorted.php b/app/Auth/Queries/AllRolesPaginatedAndSorted.php
new file mode 100644
index 000000000..add1e9e54
--- /dev/null
+++ b/app/Auth/Queries/AllRolesPaginatedAndSorted.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace BookStack\Auth\Queries;
+
+use BookStack\Auth\Role;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+/**
+ * Get all the roles in the system in a paginated format.
+ */
+class AllRolesPaginatedAndSorted
+{
+    /**
+     * @param array{sort: string, order: string, search: string} $sortData
+     */
+    public function run(int $count, array $sortData): LengthAwarePaginator
+    {
+        $sort = $sortData['sort'];
+        if ($sort === 'created_at') {
+            $sort = 'users.created_at';
+        }
+
+        $query = Role::query()->select(['*'])
+            ->withCount(['users', 'permissions'])
+            ->orderBy($sort, $sortData['order']);
+
+        if ($sortData['search']) {
+            $term = '%' . $sortData['search'] . '%';
+            $query->where(function ($query) use ($term) {
+                $query->where('display_name', 'like', $term)
+                    ->orWhere('description', 'like', $term);
+            });
+        }
+
+        return $query->paginate($count);
+    }
+}
diff --git a/app/Auth/Role.php b/app/Auth/Role.php
index 17a4edcc0..b293d1af2 100644
--- a/app/Auth/Role.php
+++ b/app/Auth/Role.php
@@ -110,14 +110,6 @@ class Role extends Model implements Loggable
         return static::query()->where('system_name', '=', $systemName)->first();
     }
 
-    /**
-     * Get all visible roles.
-     */
-    public static function visible(): Collection
-    {
-        return static::query()->where('hidden', '=', false)->orderBy('name')->get();
-    }
-
     /**
      * {@inheritdoc}
      */
diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php
index fee31ffbf..d022bf35d 100644
--- a/app/Http/Controllers/RoleController.php
+++ b/app/Http/Controllers/RoleController.php
@@ -3,6 +3,7 @@
 namespace BookStack\Http\Controllers;
 
 use BookStack\Auth\Permissions\PermissionsRepo;
+use BookStack\Auth\Queries\AllRolesPaginatedAndSorted;
 use BookStack\Auth\Role;
 use BookStack\Exceptions\PermissionsException;
 use Exception;
@@ -11,11 +12,8 @@ use Illuminate\Validation\ValidationException;
 
 class RoleController extends Controller
 {
-    protected $permissionsRepo;
+    protected PermissionsRepo $permissionsRepo;
 
-    /**
-     * PermissionController constructor.
-     */
     public function __construct(PermissionsRepo $permissionsRepo)
     {
         $this->permissionsRepo = $permissionsRepo;
@@ -24,14 +22,25 @@ class RoleController extends Controller
     /**
      * Show a listing of the roles in the system.
      */
-    public function index()
+    public function index(Request $request)
     {
         $this->checkPermission('user-roles-manage');
-        $roles = $this->permissionsRepo->getAllRoles();
+
+        $listDetails = [
+            'search' => $request->get('search', ''),
+            'sort'   => setting()->getForCurrentUser('roles_sort', 'display_name'),
+            'order'  => setting()->getForCurrentUser('roles_sort_order', 'asc'),
+        ];
+
+        $roles = (new AllRolesPaginatedAndSorted())->run(20, $listDetails);
+        $roles->appends(['search' => $listDetails['search']]);
 
         $this->setPageTitle(trans('settings.roles'));
 
-        return view('settings.roles.index', ['roles' => $roles]);
+        return view('settings.roles.index', [
+            'roles'       => $roles,
+            'listDetails' => $listDetails,
+        ]);
     }
 
     /**
@@ -75,16 +84,11 @@ class RoleController extends Controller
 
     /**
      * Show the form for editing a user role.
-     *
-     * @throws PermissionsException
      */
     public function edit(string $id)
     {
         $this->checkPermission('user-roles-manage');
         $role = $this->permissionsRepo->getRoleById($id);
-        if ($role->hidden) {
-            throw new PermissionsException(trans('errors.role_cannot_be_edited'));
-        }
 
         $this->setPageTitle(trans('settings.role_edit'));
 
diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php
index 9b089c29a..bd69aa8f5 100644
--- a/app/Http/Controllers/UserController.php
+++ b/app/Http/Controllers/UserController.php
@@ -251,7 +251,7 @@ class UserController extends Controller
      */
     public function changeSort(Request $request, string $id, string $type)
     {
-        $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users'];
+        $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles'];
         if (!in_array($type, $validSortTypes)) {
             return redirect()->back(500);
         }
@@ -318,7 +318,13 @@ class UserController extends Controller
         $this->checkPermissionOrCurrentUser('users-manage', $userId);
 
         $sort = $request->get('sort');
-        if (!in_array($sort, ['name', 'created_at', 'updated_at', 'default', 'email', 'last_activity_at'])) {
+        // TODO - Need to find a better way to validate sort options
+        //   Probably better to do a simple validation here then validate at usage.
+        $validSorts = [
+            'name', 'created_at', 'updated_at', 'default', 'email', 'last_activity_at', 'display_name',
+            'users_count', 'permissions_count',
+        ];
+        if (!in_array($sort, $validSorts)) {
             $sort = 'name';
         }
 
diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php
index d4d6d3bae..e8978d41e 100755
--- a/resources/lang/en/settings.php
+++ b/resources/lang/en/settings.php
@@ -133,6 +133,11 @@ return [
     // Role Settings
     'roles' => 'Roles',
     'role_user_roles' => 'User Roles',
+    'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
+    'roles_x_users_assigned' => '1 user assigned|:count users assigned',
+    'roles_x_permissions_provided' => '1 permission|:count permissions',
+    'roles_assigned_users' => 'Assigned Users',
+    'roles_permissions_provided' => 'Provided Permissions',
     'role_create' => 'Create New Role',
     'role_create_success' => 'Role successfully created',
     'role_delete' => 'Delete Role',
diff --git a/resources/views/settings/roles/index.blade.php b/resources/views/settings/roles/index.blade.php
index 4c3b5625a..6aeb16f92 100644
--- a/resources/views/settings/roles/index.blade.php
+++ b/resources/views/settings/roles/index.blade.php
@@ -12,30 +12,38 @@
                 <h1 class="list-heading">{{ trans('settings.role_user_roles') }}</h1>
 
                 <div class="text-right">
-                    <a href="{{ url("/settings/roles/new") }}" class="button outline">{{ trans('settings.role_create') }}</a>
+                    <a href="{{ url("/settings/roles/new") }}" class="button outline my-none">{{ trans('settings.role_create') }}</a>
                 </div>
             </div>
 
-            <table class="table">
-                <tr>
-                    <th>{{ trans('settings.role_name') }}</th>
-                    <th></th>
-                    <th class="text-center">{{ trans('settings.users') }}</th>
-                </tr>
-                @foreach($roles as $role)
-                    <tr>
-                        <td><a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
-                        <td>
-                            @if($role->mfa_enforced)
-                                <span title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </span>
-                            @endif
-                            {{ $role->description }}
-                        </td>
-                        <td class="text-center">{{ $role->users->count() }}</td>
-                    </tr>
-                @endforeach
-            </table>
+            <p class="text-muted">{{ trans('settings.roles_index_desc') }}</p>
 
+            <div class="flex-container-row items-center justify-space-between gap-m mt-m mb-l wrap">
+                <div>
+                    <div class="block inline mr-xs">
+                        <form method="get" action="{{ url("/settings/roles") }}">
+                            <input type="text" name="search" placeholder="{{ trans('common.search') }}" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif>
+                        </form>
+                    </div>
+                </div>
+                <div class="justify-flex-end">
+                    @include('common.sort', ['options' => [
+                        'display_name' => trans('common.sort_name'),
+                        'users_count' => trans('settings.roles_assigned_users'),
+                        'permissions_count' => trans('settings.roles_permissions_provided'),
+                    ], 'order' => $listDetails['order'], 'sort' => $listDetails['sort'], 'type' => 'roles'])
+                </div>
+            </div>
+
+            <div class="item-list">
+                @foreach($roles as $role)
+                    @include('settings.roles.parts.roles-list-item', ['role' => $role])
+                @endforeach
+            </div>
+
+            <div class="mb-m">
+                {{ $roles->links() }}
+            </div>
 
         </div>
     </div>
diff --git a/resources/views/settings/roles/parts/roles-list-item.blade.php b/resources/views/settings/roles/parts/roles-list-item.blade.php
new file mode 100644
index 000000000..43e8dc81a
--- /dev/null
+++ b/resources/views/settings/roles/parts/roles-list-item.blade.php
@@ -0,0 +1,14 @@
+<div class="item-list-row flex-container-row py-xs items-center">
+    <div class="py-xs px-m flex-2">
+        <a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a><br>
+        @if($role->mfa_enforced)
+            <small title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </small>
+        @endif
+        <small>{{ $role->description }}</small>
+    </div>
+    <div class="text-right flex py-xs px-m text-muted">
+        {{ trans_choice('settings.roles_x_users_assigned', $role->users_count, ['count' => $role->users_count]) }}
+        <br>
+        {{ trans_choice('settings.roles_x_permissions_provided', $role->permissions_count, ['count' => $role->permissions_count]) }}
+    </div>
+</div>
\ No newline at end of file
diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php
index 5fda0f6c0..139ac4579 100644
--- a/resources/views/users/index.blade.php
+++ b/resources/views/users/index.blade.php
@@ -10,7 +10,7 @@
             <div class="flex-container-row wrap justify-space-between items-center">
                 <h1 class="list-heading">{{ trans('settings.users') }}</h1>
                 <div>
-                    <a href="{{ url("/settings/users/create") }}" class="outline button mt-none">{{ trans('settings.users_add_new') }}</a>
+                    <a href="{{ url("/settings/users/create") }}" class="outline button my-none">{{ trans('settings.users_add_new') }}</a>
                 </div>
             </div>