Fix: improve accessibility for icon buttons (database menu) (#47531)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix (accessibility improvement)

## What is the current behavior?

Icon-only buttons do not have explicit accessible names for screen
readers or tooltips.

## What is the new behavior?

All icon-only buttons now have explicit accessible names using visually
hidden text (sr-only), ensuring proper screen reader support.

## Additional context

Tooltip text is preserved or added for visual users.
No visual changes were introduced.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Tightened end-to-end checks for policy creation so policy names must
match exactly in the list.
* Improved validation coverage for SELECT, INSERT, UPDATE, and DELETE
policy flows, reducing the chance of false-positive test matches.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
ChloeGarciaMillerand authored and GitHub committed 2026-07-06 16:59:40 +02:00
1 parent 623a9230bd
commit c87f673c4c
5 files changed
+62 -35

No files matched your search

@@ -14,6 +14,9 @@ import {
TableHead,
TableHeader,
TableRow,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
@@ -264,13 +267,18 @@ export const Indexes = () => {
View definition
</Button>
{!isSchemaLocked && (
<Button
aria-label="Delete index"
variant="text"
className="px-1"
icon={<Trash />}
onClick={() => setDeleteIndexId(index.name)}
/>
<Tooltip>
<TooltipTrigger asChild>
<Button
aria-label="Delete index"
variant="text"
className="px-1"
icon={<Trash />}
onClick={() => setDeleteIndexId(index.name)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Delete index</TooltipContent>
</Tooltip>
)}
</div>
</TableCell>
@@ -121,14 +121,20 @@ export const PolicyRow = ({
<TableCell className="text-right whitespace-nowrap">
{!isLocked && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1.5"
icon={<MoreVertical />}
data-testid={`policy-${policy.name}-actions-button`}
/>
</DropdownMenuTrigger>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1.5"
icon={<MoreVertical />}
data-testid={`policy-${policy.name}-actions-button`}
aria-label={`${policy.name} actions`}
/>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">More options</TooltipContent>
</Tooltip>
<DropdownMenuContent side="bottom" align="end" className="w-52">
<DropdownMenuItem className="gap-x-2" onClick={() => onSelectEditPolicy(policy)}>
<Edit size={14} />
@@ -17,6 +17,9 @@ import {
FormControl,
FormField,
Switch,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
@@ -154,14 +157,19 @@ export const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps
</CollapsibleTrigger>
{!disabled && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1"
icon={<MoreVertical />}
aria-label={`${role.name} actions`}
/>
</DropdownMenuTrigger>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1"
icon={<MoreVertical />}
aria-label={`${role.name} actions`}
/>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">More options</TooltipContent>
</Tooltip>
<DropdownMenuContent side="bottom" align="end" className="w-[120px]">
<DropdownMenuItem
className="space-x-2"
@@ -514,14 +514,19 @@ export const TableList = ({
{!isSchemaLocked && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1"
icon={<MoreVertical />}
aria-label={`Table ${x.name} actions`}
/>
</DropdownMenuTrigger>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="default"
className="px-1"
icon={<MoreVertical />}
aria-label={`Table ${x.name} actions`}
/>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">More options</TooltipContent>
</Tooltip>
<DropdownMenuContent side="bottom" align="end" className="w-40">
<DropdownMenuItem
className="flex items-center space-x-2"
+4 -4
View File
@@ -231,7 +231,7 @@ test.describe('RLS Policies', () => {
).toBeVisible({ timeout: 50000 })
// Verify policy appears in the list
await expect(page.getByRole('button', { name: policySelectName })).toBeVisible()
await expect(page.getByRole('button', { name: policySelectName, exact: true })).toBeVisible()
// Verify policy details
const policyRow = page.locator(`tr:has-text("${policySelectName}")`)
@@ -288,7 +288,7 @@ test.describe('RLS Policies', () => {
})
// Verify policy appears with correct details
await expect(page.getByRole('button', { name: policyInsertName })).toBeVisible()
await expect(page.getByRole('button', { name: policyInsertName, exact: true })).toBeVisible()
const policyRow = page.locator(`tr:has-text("${policyInsertName}")`)
await expect(policyRow.locator('code').filter({ hasText: /^INSERT$/ })).toBeVisible()
await expect(policyRow.locator('code').filter({ hasText: /^authenticated$/ })).toBeVisible()
@@ -349,7 +349,7 @@ test.describe('RLS Policies', () => {
})
// Verify policy appears
await expect(page.getByRole('button', { name: policyUpdateName })).toBeVisible()
await expect(page.getByRole('button', { name: policyUpdateName, exact: true })).toBeVisible()
const policyRow = page.locator(`tr:has-text("${policyUpdateName}")`)
await expect(policyRow.locator('code').filter({ hasText: /^UPDATE$/ })).toBeVisible()
})
@@ -401,7 +401,7 @@ test.describe('RLS Policies', () => {
})
// Verify policy appears
await expect(page.getByRole('button', { name: policyDeleteName })).toBeVisible()
await expect(page.getByRole('button', { name: policyDeleteName, exact: true })).toBeVisible()
const policyRow = page.locator(`tr:has-text("${policyDeleteName}")`)
await expect(policyRow.locator('code').filter({ hasText: /^DELETE$/ })).toBeVisible()
})