fix(a11y): add aria-labels to unlabeled controls and table headers

This commit is contained in:
Jason Staack
2026-03-15 21:08:02 -05:00
parent 9ad5438860
commit b07659e2c2
3 changed files with 29 additions and 18 deletions

View File

@@ -141,6 +141,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
<select <select
value={actionFilter} value={actionFilter}
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }} onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
aria-label="Filter by action"
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent" className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
> >
{ACTION_TYPES.map((a) => ( {ACTION_TYPES.map((a) => (
@@ -157,6 +158,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
type="date" type="date"
value={dateFrom} value={dateFrom}
onChange={(e) => { setDateFrom(e.target.value); setPage(1) }} onChange={(e) => { setDateFrom(e.target.value); setPage(1) }}
aria-label="Filter from date"
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent" className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
/> />
</div> </div>
@@ -168,6 +170,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
type="date" type="date"
value={dateTo} value={dateTo}
onChange={(e) => { setDateTo(e.target.value); setPage(1) }} onChange={(e) => { setDateTo(e.target.value); setPage(1) }}
aria-label="Filter to date"
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent" className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
/> />
</div> </div>
@@ -180,6 +183,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
placeholder="Search user..." placeholder="Search user..."
value={userSearch} value={userSearch}
onChange={(e) => setUserSearch(e.target.value)} onChange={(e) => setUserSearch(e.target.value)}
aria-label="Filter by user"
className="h-8 rounded-md border border-border bg-surface pl-7 pr-2 text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent w-40" className="h-8 rounded-md border border-border bg-surface pl-7 pr-2 text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent w-40"
/> />
</div> </div>
@@ -223,23 +227,23 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-border bg-elevated/50"> <tr className="border-b border-border bg-elevated/50">
<th className="w-8 px-3 py-2" /> <th scope="col" className="w-8 px-3 py-2"><span className="sr-only">Expand</span></th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
Timestamp Timestamp
</th> </th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
User User
</th> </th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
Action Action
</th> </th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
Resource Resource
</th> </th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
Device Device
</th> </th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted"> <th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
IP Address IP Address
</th> </th>
</tr> </tr>
@@ -271,6 +275,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
setPerPage(Number(e.target.value)) setPerPage(Number(e.target.value))
setPage(1) setPage(1)
}} }}
aria-label="Rows per page"
className="h-7 rounded border border-border bg-surface px-1.5 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent" className="h-7 rounded border border-border bg-surface px-1.5 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
> >
{PER_PAGE_OPTIONS.map((n) => ( {PER_PAGE_OPTIONS.map((n) => (
@@ -289,16 +294,18 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
<button <button
onClick={() => setPage(1)} onClick={() => setPage(1)}
disabled={page <= 1} disabled={page <= 1}
aria-label="First page"
className="rounded p-1 hover:bg-elevated disabled:opacity-30" className="rounded p-1 hover:bg-elevated disabled:opacity-30"
> >
<ChevronsLeft className="h-4 w-4" /> <ChevronsLeft className="h-4 w-4" aria-hidden="true" />
</button> </button>
<button <button
onClick={() => setPage(page - 1)} onClick={() => setPage(page - 1)}
disabled={page <= 1} disabled={page <= 1}
aria-label="Previous page"
className="rounded p-1 hover:bg-elevated disabled:opacity-30" className="rounded p-1 hover:bg-elevated disabled:opacity-30"
> >
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" aria-hidden="true" />
</button> </button>
<span className="px-2"> <span className="px-2">
Page {page} of {totalPages} Page {page} of {totalPages}
@@ -306,16 +313,18 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
<button <button
onClick={() => setPage(page + 1)} onClick={() => setPage(page + 1)}
disabled={page >= totalPages} disabled={page >= totalPages}
aria-label="Next page"
className="rounded p-1 hover:bg-elevated disabled:opacity-30" className="rounded p-1 hover:bg-elevated disabled:opacity-30"
> >
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" aria-hidden="true" />
</button> </button>
<button <button
onClick={() => setPage(totalPages)} onClick={() => setPage(totalPages)}
disabled={page >= totalPages} disabled={page >= totalPages}
aria-label="Last page"
className="rounded p-1 hover:bg-elevated disabled:opacity-30" className="rounded p-1 hover:bg-elevated disabled:opacity-30"
> >
<ChevronsRight className="h-4 w-4" /> <ChevronsRight className="h-4 w-4" aria-hidden="true" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -81,6 +81,7 @@ export function DeviceFilters({ tenantId }: DeviceFiltersProps) {
ref={inputRef} ref={inputRef}
className="pl-8" className="pl-8"
placeholder="Search hostname, IP..." placeholder="Search hostname, IP..."
aria-label="Search devices"
defaultValue={searchText} defaultValue={searchText}
onChange={(e) => handleSearch(e.target.value)} onChange={(e) => handleSearch(e.target.value)}
/> />

View File

@@ -77,13 +77,13 @@ export function UserList({ tenantId }: Props) {
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-border bg-surface"> <tr className="border-b border-border bg-surface">
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Name</th> <th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Name</th>
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Email</th> <th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Email</th>
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Role</th> <th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Role</th>
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Last Login</th> <th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Last Login</th>
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Status</th> <th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Status</th>
{isTenantAdmin(currentUser) && ( {isTenantAdmin(currentUser) && (
<th className="px-3 py-2 text-xs font-medium text-text-muted w-8"></th> <th scope="col" className="px-3 py-2 text-xs font-medium text-text-muted w-8"><span className="sr-only">Actions</span></th>
)} )}
</tr> </tr>
</thead> </thead>
@@ -130,8 +130,9 @@ export function UserList({ tenantId }: Props) {
onClick={() => handleDeactivate(u.id, u.email)} onClick={() => handleDeactivate(u.id, u.email)}
className="text-text-muted hover:text-error transition-colors" className="text-text-muted hover:text-error transition-colors"
title="Deactivate user" title="Deactivate user"
aria-label={`Deactivate ${u.email}`}
> >
<UserX className="h-3.5 w-3.5" /> <UserX className="h-3.5 w-3.5" aria-hidden="true" />
</button> </button>
)} )}
</td> </td>