diff --git a/Web/Phantom.Web.Components/Tables/Cell.razor b/Web/Phantom.Web.Components/Tables/Cell.razor
new file mode 100644
index 0000000..19ca5e1
--- /dev/null
+++ b/Web/Phantom.Web.Components/Tables/Cell.razor
@@ -0,0 +1,23 @@
+@if (Url == null) {
+  <td @attributes="AdditionalAttributes">
+    @ChildContent
+  </td>
+}
+else {
+  <td class="p-0" @attributes="AdditionalAttributes">
+    <a class="table-link" href="@Url">@ChildContent</a>
+  </td>
+}
+
+@code {
+
+  [CascadingParameter(Name = "Url")]
+  public string? Url { get; set; }
+
+  [Parameter]
+  public RenderFragment? ChildContent { get; set; }
+
+  [Parameter(CaptureUnmatchedValues = true)]
+  public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
+
+}
diff --git a/Web/Phantom.Web.Components/Tables/Table.razor b/Web/Phantom.Web.Components/Tables/Table.razor
index 3cf3ca6..e4dab25 100644
--- a/Web/Phantom.Web.Components/Tables/Table.razor
+++ b/Web/Phantom.Web.Components/Tables/Table.razor
@@ -1,7 +1,7 @@
 @typeparam TItem
 
 <div class="horizontal-scroll">
-  <table class="table align-middle@(Class.Length == 0 ? "" : " " + Class)">
+  <table class="@FullClass">
 
     <thead>
       <tr>
@@ -22,7 +22,9 @@
       <tbody>
         @foreach (var item in Items) {
           <tr>
-            @ItemRow(item)
+            <CascadingValue Name="Url" Value="@ItemUrl?.Invoke(item)">
+              @ItemRow(item)
+            </CascadingValue>
           </tr>
         }
       </tbody>
@@ -43,6 +45,25 @@
   [Parameter]
   public string Class { get; set; } = string.Empty;
 
+  private string FullClass {
+    get {
+      List<string> classes = new (4) {
+        "table",
+        "align-middle"
+      };
+
+      if (ItemUrl != null) {
+        classes.Add("table-hover");
+      }
+
+      if (Class.Length > 0) {
+        classes.Add(Class);
+      }
+      
+      return string.Join(' ', classes);
+    }
+  }
+
   [Parameter, EditorRequired]
   public RenderFragment HeaderRow { get; set; } = null!;
 
@@ -55,4 +76,7 @@
   [Parameter, EditorRequired]
   public IReadOnlyList<TItem>? Items { get; set; }
 
+  [Parameter]
+  public Func<TItem, string>? ItemUrl { get; set; } = null;
+
 }
diff --git a/Web/Phantom.Web/Pages/Agents.razor b/Web/Phantom.Web/Pages/Agents.razor
index 035e431..6adf9fa 100644
--- a/Web/Phantom.Web/Pages/Agents.razor
+++ b/Web/Phantom.Web/Pages/Agents.razor
@@ -21,39 +21,39 @@
       var usedInstances = agent.Stats?.RunningInstanceCount;
       var usedMemory = agent.Stats?.RunningInstanceMemory.InMegabytes;
     }
-    <td>
+    <Cell>
       <p class="fw-semibold">@agent.Name</p>
       <small class="font-monospace text-uppercase">@agent.Guid.ToString()</small>
-    </td>
-    <td class="text-end">
+    </Cell>
+    <Cell class="text-end">
       <ProgressBar Value="@(usedInstances ?? 0)" Maximum="@agent.MaxInstances">
         @(usedInstances?.ToString() ?? "?") / @agent.MaxInstances.ToString()
       </ProgressBar>
-    </td>
-    <td class="text-end">
+    </Cell>
+    <Cell class="text-end">
       <ProgressBar Value="@(usedMemory ?? 0)" Maximum="@agent.MaxMemory.InMegabytes">
         @(usedMemory?.ToString() ?? "?") / @agent.MaxMemory.InMegabytes.ToString() MB
       </ProgressBar>
-    </td>
-    <td class="text-condensed">
+    </Cell>
+    <Cell class="text-condensed">
       Build: <span class="font-monospace">@agent.BuildVersion</span>
       <br>
       Protocol: <span class="font-monospace">v@(agent.ProtocolVersion.ToString())</span>
-    </td>
+    </Cell>
     @if (agent.IsOnline) {
-      <td class="fw-semibold text-center text-success">Online</td>
-      <td class="text-end">-</td>
+      <Cell class="fw-semibold text-center text-success">Online</Cell>
+      <Cell class="text-end">-</Cell>
     }
     else {
-      <td class="fw-semibold text-center">Offline</td>
-      <td class="text-end">
+      <Cell class="fw-semibold text-center">Offline</Cell>
+      <Cell class="text-end">
       @if (agent.LastPing is {} lastPing) {
           <TimeWithOffset Time="lastPing" />
       }
       else {
         <text>N/A</text>
       }
-      </td>
+      </Cell>
     }
   </ItemRow>
   <NoItemsRow>
diff --git a/Web/Phantom.Web/Pages/Audit.razor b/Web/Phantom.Web/Pages/Audit.razor
index f12b93c..c27477f 100644
--- a/Web/Phantom.Web/Pages/Audit.razor
+++ b/Web/Phantom.Web/Pages/Audit.razor
@@ -21,23 +21,23 @@
     <Column Width="100%">Data</Column>
   </HeaderRow>
   <ItemRow Context="logItem">
-    <td class="text-end">
+    <Cell class="text-end">
       <TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" />
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <p class="fw-semibold">@(logItem.UserName ?? "-")</p>
       <small class="font-monospace text-uppercase">@logItem.UserGuid.ToString()</small>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <p>@logItem.EventType.ToNiceString()</p>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <p class="fw-semibold">@(logItem.SubjectId is {} subjectId && GetSubjectName(logItem.SubjectType, subjectId) is {} subjectName ? subjectName : "-")</p>
       <small class="font-monospace text-uppercase">@(logItem.SubjectId ?? "-")</small>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <code>@logItem.JsonData</code>
-    </td>
+    </Cell>
   </ItemRow>
   <NoItemsRow>
     No audit log entries found.
diff --git a/Web/Phantom.Web/Pages/Events.razor b/Web/Phantom.Web/Pages/Events.razor
index 677a6cd..575ff4c 100644
--- a/Web/Phantom.Web/Pages/Events.razor
+++ b/Web/Phantom.Web/Pages/Events.razor
@@ -22,10 +22,10 @@
     <Column Width="100%">Data</Column>
   </HeaderRow>
   <ItemRow Context="logItem">
-    <td class="text-end">
+    <Cell class="text-end">
       <TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" />
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       @if (logItem.AgentGuid is {} agentGuid) {
         <p class="fw-semibold">@(GetAgentName(agentGuid))</p>
         <small class="font-monospace text-uppercase">@agentGuid.ToString()</small>
@@ -33,15 +33,15 @@
       else {
         <text>-</text>
       }
-    </td>
-    <td>@logItem.EventType.ToNiceString()</td>
-    <td>
+    </Cell>
+    <Cell>@logItem.EventType.ToNiceString()</Cell>
+    <Cell>
       <p class="fw-semibold">@(GetSubjectName(logItem.SubjectType, logItem.SubjectId) ?? "-")</p>
       <small class="font-monospace text-uppercase">@(logItem.SubjectId)</small>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <code>@logItem.JsonData</code>
-    </td>
+    </Cell>
   </ItemRow>
   <NoItemsRow>
     No event log entries found.
diff --git a/Web/Phantom.Web/Pages/Instances.razor b/Web/Phantom.Web/Pages/Instances.razor
index 6a1db4a..d39cf9a 100644
--- a/Web/Phantom.Web/Pages/Instances.razor
+++ b/Web/Phantom.Web/Pages/Instances.razor
@@ -16,7 +16,7 @@
   <a href="instances/create" class="btn btn-primary" role="button">New Instance</a>
 </PermissionView>
 
-<Table TItem="Instance" Items="instances">
+<Table TItem="Instance" Items="instances" ItemUrl="@(static instance => "instances/" + instance.Configuration.InstanceGuid)">
   <HeaderRow>
     <Column Width="40%">Agent</Column>
     <Column Width="40%">Name</Column>
@@ -28,34 +28,31 @@
     <Column MinWidth="75px">Actions</Column>
   </HeaderRow>
   <ItemRow Context="instance">
-    @{
-      var configuration = instance.Configuration;
-      var agentName = agentNamesByGuid.TryGetValue(configuration.AgentGuid, out var name) ? name : string.Empty;
-    }
-    <td>
-      <p class="fw-semibold">@agentName</p>
+    @{ var configuration = instance.Configuration; }
+    <Cell>
+      <p class="fw-semibold">@(agentNamesByGuid.TryGetValue(configuration.AgentGuid, out var name) ? name : string.Empty)</p>
       <small class="font-monospace text-uppercase">@configuration.AgentGuid.ToString()</small>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <p class="fw-semibold">@configuration.InstanceName</p>
       <small class="font-monospace text-uppercase">@configuration.InstanceGuid.ToString()</small>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <InstanceStatusText Status="instance.Status" />
-    </td>
-    <td>@configuration.MinecraftServerKind @configuration.MinecraftVersion</td>
-    <td class="text-center">
+    </Cell>
+    <Cell>@configuration.MinecraftServerKind @configuration.MinecraftVersion</Cell>
+    <Cell class="text-center">
       <p class="font-monospace">@configuration.ServerPort.ToString()</p>
-    </td>
-    <td class="text-center">
+    </Cell>
+    <Cell class="text-center">
       <p class="font-monospace">@configuration.RconPort.ToString()</p>
-    </td>
-    <td class="text-end">
+    </Cell>
+    <Cell class="text-end">
       <p class="font-monospace">@configuration.MemoryAllocation.InMegabytes.ToString() MB</p>
-    </td>
-    <td>
+    </Cell>
+    <Cell>
       <a href="instances/@configuration.InstanceGuid.ToString()" class="btn btn-info btn-sm">Detail</a>
-    </td>
+    </Cell>
   </ItemRow>
   <NoItemsRow>
     No instances found.
diff --git a/Web/Phantom.Web/Pages/Users.razor b/Web/Phantom.Web/Pages/Users.razor
index dd0eb25..8d82028 100644
--- a/Web/Phantom.Web/Pages/Users.razor
+++ b/Web/Phantom.Web/Pages/Users.razor
@@ -28,20 +28,20 @@
       </HeaderRow>
       <ItemRow Context="user">
         @{ var isMe = me == user.Guid; }
-        <td>
+        <Cell>
           <p class="fw-semibold">@user.Name</p>
           <small class="font-monospace text-uppercase">@user.Guid.ToString()</small>
-        </td>
-        <td>
+        </Cell>
+        <Cell>
           @(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")
-        </td>
+        </Cell>
         @if (canEdit) {
-          <td>
+          <Cell>
             @if (!isMe) {
               <button class="btn btn-primary btn-sm" @onclick="() => userRolesDialog.Show(user)">Edit Roles</button>
               <button class="btn btn-danger btn-sm" @onclick="() => userDeleteDialog.Show(user)">Delete...</button>
             }
-          </td>
+          </Cell>
         }
       </ItemRow>
       <NoItemsRow>
diff --git a/Web/Phantom.Web/wwwroot/css/site.css b/Web/Phantom.Web/wwwroot/css/site.css
index b833ef2..734d141 100644
--- a/Web/Phantom.Web/wwwroot/css/site.css
+++ b/Web/Phantom.Web/wwwroot/css/site.css
@@ -60,16 +60,23 @@ code {
 .table {
   margin-top: 0.5rem;
   white-space: nowrap;
+  --bs-table-hover-bg: rgba(15, 100, 119, 0.05);
 }
 
 .table > :not(:first-child) {
   border-top: 2px solid #a6a6a6;
 }
 
-.table th, .table td {
+.table th, .table td, .table td > .table-link {
   padding: 0.5rem 1.25rem;
 }
 
+.table .table-link {
+  display: block;
+  color: inherit;
+  text-decoration: none;
+}
+
 .table p {
   margin: 0;
 }